Недавно один знакомый, которого я знаю через совсем не программистские круги, попросил помочь ему с лабораторной по C++. В коде было примерно следующее:
Этот случай напомнил о том, что тема указателей является едва ли не основным источником ошибок у изучающих язык студентов, а заодно — своеобразным перевалом, преодоление которого сопряжено с «щелчком» в голове, вызывать который, увы, умеют не все преподаватели. Надеюсь, предложенная ниже лингвистическая аналогия поможет нынешним студентам постигнуть эту концепцию, а их преподавателям или друзьям — донести до них это знание.
Лет десять назад на одном форуме была загадана детская, вроде, загадка:
Смотрите. Чтобы понять, что такое «еда обеда людоедоедоеда», нам нужно произвести лингвистический анализ последнего слова. Проще всего это сделать, читая корни справа налево:
По удачному стечению обстоятельств, тот же порядок чтения — справа налево — применяется в C/C++ для ссылочных типов данных:
С правой частью разобрались, теперь принимаемся за левую.
Стало быть, людоедоедовед (= тот, кто ведает теми, кто ест людоедов) пригласила на обед людоеда. Очевидно, цель такого приглашения — покормить своих подведомственных людоедоедов.
То есть, лингвистическое правило оказывается таковым: поставив слово «еда» или «обед» слева от слова, оканчивающегося корнем «ед», мы скидываем этот последний корень, оставляя только словарную основу до него.
Но аналогично же ведут себя и указатели в C/C++: если слева от сущности, имеющий тип «указатель на T», или «T*», мы поставим символ указателя (ту самую звёздочку), то в результате мы скинем один лишний указатель из типа, получив, собственно, T (или, если быть совсем точным, T&, но темы ссылок и прочих lvalue я сейчас намеренно не хочу касаться). Совсем грубо (игнорируя некоторые правила синтаксиса) можно записать, что
Что в итоге? Я не сформулировал никакого чёткого правила и не сказал про указатели ничего нового. Я лишь надеюсь, что эта лингвистическая аналогия поможет кому-либо из изучающих C/C++ быстрее понять эту тему. Если принять звёздочку за еду, а Люду — за базовый тип… Вы поняли.
void do_something(MyObj *input[], int count)
{
MyObj **copy = new MyObj*[count];
for (int i = 0; i < count; ++i)
*copy[i] = *input[i];
...
}
Этот случай напомнил о том, что тема указателей является едва ли не основным источником ошибок у изучающих язык студентов, а заодно — своеобразным перевалом, преодоление которого сопряжено с «щелчком» в голове, вызывать который, увы, умеют не все преподаватели. Надеюсь, предложенная ниже лингвистическая аналогия поможет нынешним студентам постигнуть эту концепцию, а их преподавателям или друзьям — донести до них это знание.
Лет десять назад на одном форуме была загадана детская, вроде, загадка:
Для чего еду обедаЯ хочу показать, что эта загадка имеет самое прямое отношение к C/C++, поскольку тема указателей легко может быть разобрана по аналогии.
Людоедоедоеда
Пригласила на обед
Людоедоедовед?
Смотрите. Чтобы понять, что такое «еда обеда людоедоедоеда», нам нужно произвести лингвистический анализ последнего слова. Проще всего это сделать, читая корни справа налево:
- Людоед = тот, кто ест людей.
- Людоедоед = тот, кто ест людоедов = тот, кто ест тех, кто ест людей.
- Людоедоедоед = тот, кто ест людоедоедов = тот, кто ест тех, кто ест людоедов = тот, кто ест тех, кто ест тех, кто ест людей.
По удачному стечению обстоятельств, тот же порядок чтения — справа налево — применяется в C/C++ для ссылочных типов данных:
- MyClass* = указатель на MyClass.
- MyClass** = указатель на MyClass* = указатель на указатель на MyClass.
- MyClass*** = указатель на MyClass** = указатель на указатель на MyClass* = указатель на указатель на указатель на MyClass.
С правой частью разобрались, теперь принимаемся за левую.
- Обед людоедоедоеда = обед того, кто ест людоедоедов = людоедоед.
- Еда обеда людоедоедоеда = еда людоедоеда = еда того, кто ест людоедов = людоед.
Стало быть, людоедоедовед (= тот, кто ведает теми, кто ест людоедов) пригласила на обед людоеда. Очевидно, цель такого приглашения — покормить своих подведомственных людоедоедов.
То есть, лингвистическое правило оказывается таковым: поставив слово «еда» или «обед» слева от слова, оканчивающегося корнем «ед», мы скидываем этот последний корень, оставляя только словарную основу до него.
Но аналогично же ведут себя и указатели в C/C++: если слева от сущности, имеющий тип «указатель на T», или «T*», мы поставим символ указателя (ту самую звёздочку), то в результате мы скинем один лишний указатель из типа, получив, собственно, T (или, если быть совсем точным, T&, но темы ссылок и прочих lvalue я сейчас намеренно не хочу касаться). Совсем грубо (игнорируя некоторые правила синтаксиса) можно записать, что
- *(T*) === T,
- *(T**) === T*,
- **(T**) === T,
- *(T***) === T**,
- и так далее.
Что в итоге? Я не сформулировал никакого чёткого правила и не сказал про указатели ничего нового. Я лишь надеюсь, что эта лингвистическая аналогия поможет кому-либо из изучающих C/C++ быстрее понять эту тему. Если принять звёздочку за еду, а Люду — за базовый тип… Вы поняли.
jaiprakash
А ведь есть языки, лишённые радости указателей!
ru_vlad
А может это и не плохо?
Java хороший пример. (Да, знаю что можно использовать ссылки)
barker
Какие такие ссылки?
0xd34df00d
Всякие хаскели еще лучше. Ни указателей, ни ссылок.
DASM
ни софта на них…
0xd34df00d
Софта на хаскеле на моей машине поболе, чем на джаве.
Да и вам как программисту самому писать или радоваться наличию софта?
DASM
честно говоря не осилил я функциональщину. То ли статей толковых мало, то ли мозгов. Не понял фишки
0xd34df00d
Смотря что вы изучали. Менять цикл на map просто так действительно смысла не так уж много.
DASM
Ммм… какой map? std::map пользую, а Хаскель причем тут?
0xd34df00d
Ну такой вот map. Хотя лучше более общая штука, конечно.
0xd34df00d
Видимо, кому-то очень не нравится функциональщина. Ну ладно.
Vladislav_Dudnikov
Я думаю дело в другом.
Представьте, что вы веган, что бы вы говорили людям?
0xd34df00d
В треде об особенностях еды можно было бы и обсудить причины, побудившие бы меня быть веганом. Самое оно, как по мне.
И посмотрите на самый первый комментарий этой ветки.
Vladislav_Dudnikov
Я просто пошутил.
0xd34df00d
У меня всё очень плохо с восприятием шуток, прошу простить.
DASM
Ну на самом деле Вы двигаете функциональные языки, словно это какая-то панацея. Все мои потуги что-то написать напоминают мне попытки забить шуруп молотком. У моих объектов в программах есть состояния, которые надо хранить — и то, что я прочел о ф-ных языках напоминает мне костыли. Мои проги пишутся для реального мира — embedded. И куда там это воткнуть — совершенно не понимаю (даже если были бы компиляторы соответсвующие). Простейшая задачка становится математическим ребусом. Вероятно есть, и может даже много, задач, где ф-ное программирование в тему. Я пока что не вижу таких, и судя по минусам Вам — не я один.
Druu
Ну так естественно в этом случае у вас будет ощущение забивания шурупом молотка. Функциональщина нужна не за тем, чтоб описывать состояние, а за тем, чтобы состояния не было.
Тогда вам точно функциональщина не нужна. Ее смысл в итоге сводится к декларативности (вместо того, чтобы описывать процесс получения результата, описываем сам результат, а уже компуктер догадается, как его получить). В embedded вам нужно описывать процесс by design.
0xd34df00d
Неа. Если достаточно упороться монадками, то наступает просветление и понимание, что чистоты нет, стейта нет, эффектов нет, потому что это сами по себе не дискриминирующие термины.
State
— это просто удобная абстракция некоего паттерна композиции функций, чтобы создать видимость объективно существующего стейта. Но мы ведь понимаем, что это ерунда.Программа, производящая
IO
-экшн, вполне себе чистая. Ну, просто как набор правил, как этот экшн произвести. Точно так же, как, на самом деле, и программа на С. Просто в программе на С у вас весь код — одно большоеIO
.Поэтому что важно — возможность себя ограничить. Если вы видите функцию, которая живёт не в IO, то вы точно1 знаете, что файлы она читать-писать не будет и на сервер не постучится. Если вы видите функцию, которая живёт в
State s
, то вы точно знаете, чем ограничено её «состояние».Ну я тут, короче, в очередной раз за систему типов и всё такое топлю.
unsafePerformIO
или всякие тамSafeHaskell
включите, но это несущественно в рамках дискуссии.Druu
Ну так стейта нет — это то, о чем я и говорю. Если же у вас стейт есть, и вы пытаетесь его делать на ФП — то что-то не так. В смысле самого мышления.
Только DSL и верификация ортогональны функциональному программированию. Вообще, конечно, термин сейчас сильно размыт и следует по-хорошему уточнять, что под фп подразумевается. Я подразумеваю в базе чистую лямбда с надстройками. То есть какой-нибудь Scheme без макросов — сферическое ФП в вакууме.
0xd34df00d
Я про то, что его вообще нет в том смысле, что это бессмысленный термин сам по себе.
Вот вы fold или unfoldr делаете, аккумулятор — это стейт?
Я вот на Charts с их монадическим Easy API графички построил, у меня есть стейт?
Или вот я вообще взял произвольную монаду и сижу в do-нотации, выковырял там из неё какой-то
x
и какой-тоy
типаУ меня здесь есть стейт, если
m ~ Maybe
иf1
иf2
соответствующие? А если теперьm ~ State s
, иf1 = gets foo, f2 = gets bar
?Ну, я имею в виду написание eDSL и использование имеющейся в языке системы типов (да, я помню нашу дискуссию на эту тему) для предоставления некоторых гарантий. Хотя какой-нибудь идрис для этих целей подойдёт ещё лучше, потому что гарантий там можно выразить ещё больше
, и do-нотация не только для Monad и Applicative.ФП такого толку — это про типы, ИМХО. Лямбда-исчисление я вам и на плюсах замутить могу. Туплы на них прикольно делаются, кстати, но то такое.
Druu
Стейт — это не то, что в коде написано, а то, как код воспринимается (от кода при этом может зависит насколько сложно описываемые им вещи рассматривать в рамках той или иной парадигмы). Если вы рассуждаете об аккумуляторе фолда как о стейте — это стейт. Если нет — то нет. Аналогично с монадами. Если вы монадический код в IO воспринимаете как просто императивный код — ну, у вас тут стейт.
А можно типы в императивщину добавить. И верифицировать. Или верифицировать без типов. Все-таки тут речь надо вести не просто о типах, а об определенном способе мышления и использования этих типов. В этом контексте правильно говорить не "фп — это когда есть А и Б", а, скорее, "фп — это когда нету А и Б" (и аналогично для любой другой парадигмы). Потому что именно отсутствие альтернатив вынуждает строить программу и рассуждать о ней определенным способом. Наличие же фич — лишь дает возможность.
0xd34df00d
Хм, это интересный взгляд, надо подумать. Правда, мне не очень нравится некоторая его субъективность или зависимость от наблюдателя. Но, может, это и неплохо.
Ну вы и на хаскеле можете всё писать в IO с IORef'ами, никто не запретит.
А если вы как-то так внезапно добавите в C++ такую систему типов, что можно будет гарантировать чистоту, иммутабельность и прочие дорогие сердцу вещи, то да, у вас будет функциональное подмножество.
babylon
Функция — это объект внутри которого выполняется некоторый код и куда опционально передаются параметры или возвращается результат. В этом смысле ФП это по прежнему ООП.
Druu
Одно и то же можно сказать разными способами. То, что можно сказать в ФП, можно сказать и в ООП. И наоборот.
babylon
Есть ЯП в которых нет объектов. Так что ваш тезис и тут некорректен.
Druu
Но это не мешает в них объекты использовать, моделируя на имеющихся примитивах. Точно так же как в ООП нет функций, но вы можете их использовать, моделируя при помощи объектов.
babylon
Программирование на JSONNET это ООП?
Druu
Нет, если по парадигмам, то это ближе всего к ФП, наверное. От ООП там вроде и вовсе ничего нету.
babylon
Верно, но тем не менее там сполошь и рядом объекты. В их построении не хватает сингулярности. Но это уже другая важная тема. Которую все старательно обходят стороной.
Druu
Это вы к чему, с-но?
babylon
К тому что массивы еще как то в объекты собрать можно, а вот функции нет. На уровне компилятора. И классы кстати тоже:) Lisp в этом ряду стоит обособоленно.
Druu
Можно, т.к. язык тьюринг-полный. Только, думаю, кодировка вам откровенно не понравится :)
babylon
Нет нельзя. Нельзя отделить контекст от параметров.
Druu
> Нет нельзя.
Можно.
babylon
"fun":[1,2] — плс! хочу функцию:)
0xd34df00d
А что она должна делать?
Druu
Чего, не понял?
0xd34df00d
Я вот вообще не понял, что означает собирание массивов в объекты, и чем функции хуже массивов.
babylon
Самый простой вариант.
Функция это уже объект.
0xd34df00d
Неа, это не панацея. Есть области, в которых функциональные языки на текущем уровне развития — исключительно хреновый выбор. Какие-нибудь числодробилки, скажем, или задачи, где нужно просто очень много памяти. Под такие задачи я тоже беру C++ и счастливо пишу на нём.
Но в остальных задачах, от, не знаю, какой-нибудь банальной обработки логов до написания компиляторов и всяких сервисов-микросервисов — да, функциональщина — очень хороший выбор. И с написанием стейтфул-программ там тоже всё вполне хорошо (не знаю, чем вам тот же State — костыль). А с многопоточностью ещё лучше, начиная от принципиального отсутствия класса проблем, связанных с разделяемыми данными, и заканчивая такими примитивами, как STM, например.
Иными словами, мне проще перечислить задачи, где чистое ФП будет плохим выбором, чем где оно будет хорошим. Короче список получится.
С эмбеддедом у меня мало опыта, правда, не знаю про актуальное состояние дел, но слышал про истории успеха с этим, например.
DASM
там похоже совсем не то. Под эмбеддед я понимаю микроконтроллеры. Вот пишут «описываем сам результат, а уже компуктер догадается, как его получить).» Совершенно не понимаю. Вот сидит себе микроконтроллер. Ждет пакет инфы с радиоэфира. По получении должен сделать то, потом то, все со строгой времянкой и в зависимости от состоянии кучи датчиков. Как тут описать результат — непонятно. Более менее все ясно с числами Фиббоначи только мне. Да и то, там рекурсия (?) за которую в embedded руки отрывают — мало ресурсов. Даже за динамическое выделение памяти не всегда по голове гладят. По-моему язык пригоден для математиков, задачи реального мира если и можно решить, то выглядит это как несчастная сова на глобусе. Но я не сильно вникал в ФП
0xd34df00d
Вы про тот же Ivory? Я его не тыкал совсем, так, вместе с вами сейчас документацию читаю.
Это уже пролог какой-то. Я бы сказал, что в функциональщине предполагаемая догадливость компуктера сильно меньше.
Не, почему? Ivory вполне может компилировать это вот
n `times` f
во вполне себе императивный цикл. Собственно, подробное описание этого примера ровно об этом и говорит: «Here’s an Ivory procedure which computes Fibonacci numbers by mutating values on the stack in a loop.» На стеке. В цикле.«Systems Programming: Ivory is well suited for writing programs which interact directly with hardware and do not require dynamic memory allocation.»
Для математиков пригодны более другие языки, кстати, в хаскеле есть кое-какие криво сделанные вещи, из-за которых в некоторых областях конкретно он будет тоже очень плохим выбором (ну там, формальное доказательство теорем всякое, всё такое). А про реальные задачи, ну, я уже писал.
DASM
OK, спасибо за пояснения, думаю мне дергаться на этот счет рано. Вот что реально заставляет нервничать — это С++ со всеми его нововведениями, по-моему они задались целью сделать самый сложный язык на свете. Глядя на некоторые новые исходники на С++ хочется спросить «на каком это языке.» Впрочем отвлекся.
0xd34df00d
Ну, у меня есть знакомые товарищи, которые сидят на C++03 и в ус не дуют, им норм, так что это зависит.
Я, впрочем, и плюсы люблю нежной любовью, люблю обмазаться свежими темплейтами и компилировать. Из всех нововведений меня только
std::launder
раздражает. Но это дело такое, да.Druu
Ну видите, "должен сделать" — это уже не ФП. С точки зрения ФП — будет понятие некоего абстрактного действия. Вы можете создать действие, скомбинировать это действие вместе с какими-то другими действиями каким-то способом и получить новое действие. Пакет инфы и показания датчиков — это данные. Вы описываете, действие какого вида должно получится из каких данных, то есть описываете ф-ю (в математическом смысле) perform: Data -> Action, при этом не специфицируете, как эту ф-ю требуется вычислять. Далее вы можете ввести какие-то проверки на тот факт, что построенный на указанных данных Action удовлетворяет определенным условиям, например, у вас есть ф-я time Action -> int и вам надо убедиться, что time(perform(data)) < n. При этом если вы свой Action составляли из других экшонов при помощи некоторых комбинаторов, то у вас могут быть разные утверждения вроде (time(action1) = a, time(action2) = b => time(sequence(action1, action2)) = a + b); Вот если вы так все будете воспринимать — вы пишете на фп, при этом в качестве языка можете хоть сишку использовать.
DASM
боюсь что просмотр итогового кода (в дизасме) может привести к инфаркту, не?
Druu
Ну почему? Вы же можете выбрать комбинаторы близкие по смыслу к композиции существующих блоков кода на асме. В пределе — вообще совпадающими, тогда рассматриваемая штука будет просто средой метапрограммирования над асмом.
0xd34df00d
Кстати, нет, компиляторы-то уже вполне умные.
Парсеры на хаскелевском attoparsec приближаются по скорости к boost.spirit, который вообще один из быстрейших вариантов. Забавные бенчмарки бинарных парсеров я тоже видел, где cereal, что ли, уделывает аналогичный высокопроизводительный сишный парсер. Увы, сейчас не нагуглю.
А когда вчера мне компилятор функцию вида
скомпилировал в однопроходный цикл по списку, я совсем не удивился.
F376
Функциональное Программирование (ФП) можно представить себе как такое написание программы, когда ее выполнение представляет собой как бы вложенные друг за другом «математические функции»: fun1(fun2(fun3(x))) У каждой функции есть совершенно чёткий «вход» и совершенно чёткий «выход», также как у математических (вычислительных) функций: Выход = function(Вход). При этом ни одна функция внутри не обращается к каким-либо переменным/памяти/портам/итд, короче ко всему тому, что представляет собой состояние/память. Подобное состояние, если оно есть (к примеру порт), подается только как «вход». И вдобавок к этому функция должна быть детерминирована (делать одно и то же).
Что это даёт? Подобные функции не зависят ни от чего, кроме как своих входных параметров. И поэтому, если каждый раз перезапускать их с одними и теми же аргументами, мы будем получать на выходе один и тот же результат.
Эмбедщику можно это понять так, что данные словно бы передаются в регистрах, и в регистрах же возвращаются назад, ничего более не меняя.
Это очень удобно и для отладки и для написания стабильных программ — проблемы отладки чаще всего заключаются в том что при работе программы меняется какое-то внутреннее состояние (память, переменные, стек итд) и нет возможности сделать «шаг назад». А при ФП парадигме — достаточно подать на «вход» программы одни и те же предварительно записанные данные, и она выдаст один и тот же результат, пока мы не поправим саму программу.
Парадигма такова, что программа как бы «воздействует» на проходящие через нее данные или их потоки, сама же являя собой «чистый закон».
Что интересно, правильная и рекомендуемая методология *NIX программирования это, в некотором смысле, ФП: мелкие отдельные утилиты, каждая выполняющая свою функцию, принимающие данные на вход, и выдающие детерминированно одно и то же, при том что их можно объединять друг за другом, т.е. выход одной утилиты/команды по-цепочке передавать на вход другой.
Пиксельные шейдеры тоже при определенном приближении являют собой ФП-стиль, вместе с реактивным.
Часть существующих программ и так уже написана приближаясь к функциональному стилю.
Реальное ФП, конечно, подразумевает собой гораздо большее, но кое-какие полезные элементы почерпнуть из ФП, как видим, можно.
DASM
Спасибо, достаточно понятно объяснили, хотя реальная применимость мне пока туманна. Вот допустим хотим мы смоделировать покупателя. Чтобы он что-то купил ненужное, он должен продать что то ненужное (например свой труд). Продав, он получит деньги (состояние объекта). Причем поставить покупателя в функцию покупки просто так не очень просто (эти деньги могут лежать в банке, на них идет процент итп). То есть я могу примерно это представить, но совершенно не понимаю, как в ФП реализуется что-то асинхронное, завязанное на время, возникающее от случая к случаю… А еще такой вопрос, глядя на машинный код ФП программы, вы сможете отличить его от кода, полученного компиляцией с императивного языка?
Druu
В ФП функции — это тоже данные. С-но, действия, процессы, которые при помощи каких-то примитивов комбинируются в их наборы, ивент лупы с хендлерами и любые подобные вещи — это все данные.
Yuuri
Как это, а Ptr/ForeignPtr/StablePtr? ;)
0xd34df00d
Это скорее хендлы.
0xd34df00d
Надо, наверное, сразу было пояснить. Хендлы в том смысле, что для нормального программирования на хаскеле они не нужны совсем, эти Ptr'ы вылезают только при работе с FFI, посему относиться к ним лучше не как к указателям, а как к хендлам, описывающим некоторые внешние относительно языка ресурсы.
Gutt
В Java то, что мы используем для работы с объектами, вполне себе указатели. Просто мы лишены радости изменять эти указатели или создавать новые на их основе, используя простую арифметику (к примеру, получить таким образом указатель на массив, аналогичный данному, но начинающийся с третьего элемента). Например, если в метод передали (указатель на) объект, то можно спокойно манипулировать содержимым объекта, но вот взять и назначить указателю новый адрес (объекта) так, чтобы это было видно за пределами метода, нельзя. То есть при передаче объекта в метод создаётся копия указателя, с которой метод и работает. Всё это здорово упрощает жизнь, не давая выстрелить себе в ногу на ровном месте. Примитивы при передаче в метод всегда копируются. Если очень хочется менять сам примитив из метода, то его можно обернуть в объект и уже этот объект передать в метод.
SinsI
Как такие языки решают проблему модификации «тяжёлых» объектов?
Вот надо в картинке изменить пару пикселей — не создавать же полную её копию?
0xd34df00d
Если вам нужно только изменить пару пикселей и всё, то в любом случае придётся создавать полную копию. Иначе можно удобным полудекларативным, полуимперативным API описать все необходимые изменения и потом один раз их выполнить.
Примерно так работает, например, Repa, и как-то похоже работает Accelerate.
Druu
Создание которой можно соптимизировать в мутацию, если доказать, что на объект существует лишь одна ссылка.
0xd34df00d
Что тем проще сделать, чем более строг ваш язык. Особенно если там есть аффинные типы, например.
Druu
Ну да, я, с-но, и намекал на Clean.
bvdmitri
В таких языках любой «объект» на самом деле является указателем на соответсвующий объект. Копию нужно всегда делать явно.
GoldJee
Вот иллююстрация на Java:
TheKnight
И ни фига подобного.
В зависимости от того, как сложатся звезды у вас первое сравнение в примере со String может выдать true.
А может и false.
ideone.com/BkxRUq
Artyomcool
В данном конкретном примере звезды должны бы всегда складываться в true, если пользоваться стандартным javac, а не компилить в байткод самому.
TheKnight
Спасибо за уточнение, попробую перечитать этот момент в спеке.
Artyomcool
Упрощу вам поиски: docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.5
Akon32
Дело в том, что компилятор одинаковые строковые константы в пределах класса может записывать в class-файл как одну константу (и делает это в данном частном случае). По ссылке из комментария выше это и происходит.
vics001
В Java нет указателей, но при работи с большими массивами int и индексами по ним, задача вполне логически тожественная указателям первого уровня.
Yuuri
Не так страшны указатели, как их арифметика.
amarao
До тех пор, пока у процессора есть инструкция «прочитать в регистр по ссылке», любой язык программирования, игнорирующий этот факт, будет просасывать Си по производительности.
Правильный подход: богатая система типов, запрещающая творить фигню (Rust!), но сохраняющая указатели в полном объёме.
SinsI
Вообще говоря — не факт.
Теоретически, языки более высокого уровня могут позволить применить гораздо более эффективные оптимизации, вроде многопоточности или вообще — менять применяемый алгоритм по мере накопления данных прямо во время выполнения.
evocatus
Чем JVM уже некоторое время успешно занимается.
harlong
А если разобрать несколько иначе (людоедо_едо_ед = тот, кто ест еду людоеда = собственно, людоед), то его обед это человек, а еда обеда — обычная человеческая еда. Которую ученая, изучающая людей, пригласила с очевидной целью — съесть. )
mk2
В таком случае вы промежуточно имеете разбиение людоедо_едоед, а слово «едоед» в русском языке отсутствует.
harlong
Формально да. При первом прочтении стишка у меня оно в голове само построилось по аналогии с «мясоед» и подобными.
netch80
Вполне может быть, что людоед — лев, людоедоед — блоха, людоедоедоед — лягушка.
Тогда людоедоедоед != людоеду.
Ну а энтомолог Иванова пригласила льва, чтобы посчитать блох ;)
mk2
Блоха львов всё же не ест. Так, надкусывает.
funca
«Любую проблему можно решить путём введения дополнительного уровня абстракции, кроме проблемы слишком большого количества уровней абстракции»
picul
А в чем парадокс?
Gaernebjorn
Простите, но сразу стало непонятно. Самый правый корень "ед". Может слева направо?
Furax Автор
Как раз справа налево. Правый корень «ед» заменяем на фразу «тот, кто ест», словарная основа остаётся. Также как «T*» читается как «указатель на T», то есть, справа налево.
Gaernebjorn
Вы сейчас пытаетесь объяснить рекурсивно, через указатели :). Я же говорю о логике объяснения в статье.
Если мы разбиваем слово людоедоедоед, тогда первая строчка (по Вашему описанию) должна быть
То есть у вас словесный алгоритм не соответствует написанному коду. А для вот таких вот объяснений-аналогий это как раз самое важное.
Chamie
Вы имеете в виду, что строки в другом порядке должны быть? Так эти строки — не пошаговое выполнение метода, а просто иллюстрация его на всё более сложных примерах.
Gutt
Забудьте. Гораздо проще разобрать эту загадку, используя указатели из C, чем наоборот.
Zuy
А что, обьяснения, что указатель — это адрес в памяти, где лежит объект не достаточно? Подход в статье ну совсем какой-то мутный.
Furax Автор
Кому-то достаточно. Лингвистическая аналогия — исключительно для тех, кто не понял с первого раза.
Antervis
подавляющее большинство с первого раза не поймет ни сухое объяснение через адреса/номера ячеек, ни тем более вашу эм… аналогию. ~90% — визуалы, и объяснять в большинстве случаев надо через рисунок; остальные же редко идут в точные науки, требующие пространственного мышления.
Zuy
А если непонимающему просто дать задачу, где указатели реально нужны и он сам придет к их необходимости.
Мне кажется, нет смысла объяснять указатели дальше одного уровня.
Вещи типа
TObject* objects[]
должны как-то сами приходить.Furax Автор
Я и говорю: нужен щелчок.
robert_ayrapetyan
Нет конечно, отсюда и все эти бесконечные статьи. Указатель — это переменная, содержащая адрес, а не сам адрес. Упрощая, вы вводите новичка в заблуждение. Отсюда всякие непонятки с арифметикой над указателями и пр. ((int*)55555+1 = 55563 на 64-х битных платформах). Лучше сказать так: указатель — это некая абстракция над местоположением некоторой сущности в памяти (зависит от реализации, это может быть вообще какой-то дескриптор на экзотических архитектурах, а на интелах — это смещение внутри сегмента, даже здесь не назовешь это просто адресом).
khim
На самом деле как ни говори, а проблема в том, что указатель — это вещь, которая объединяет несколько разных сущностей, ни об одной из которых новочёк понятия не имеет. Вот все эти «адреса», «память» и прочее — для него «тёмный лес и куча дров».
Лучшее, что я видел — это «отодвинуть» изучение указателей за ту точку, когда человек уже знает для чего они нужны.
То есть вначале мы изучаем массивы, структуры, дальше изучаем алгоритмы (деревья, очереди с приоритетами). А потом, когда человек уже привык оперировать с индексами, которые указывают на ячейки, в которых тоже лежат индексы — объяснить ему, что указатель — это такой «синтаксический сахар» для большого массива с названием «опереативная память» — дело пяти минут.
И всё. И нет никакой магии и никакого «щелчка» в голове, всё буднично и банально.
Antervis
подскажите, как сделать дерево без указателей?
Rsa97
Пр изучении программирования деревья (да и списки, стеки, деки, очереди) обычно вводят на базе массива. Заодно показывается принцип сборки мусора.
khim
Это не «обычно». Это «как надо». Потому что «обычно» стремяться перескочить через все «скучные» вещи и научить какие-нибудь картинки рисовать. Или web-страничку генерировать.
Почему-то никого не удручает тот факт, что для того, чтобы научиться рассказы писать нужно потратить не один год, рисуя палочки и изучая как подлежащее со сказуемым согласуется.
А потратить пару месяцев на то, чтобы понять как устроены базовые, простейшие структуры данных (и на их основе понять как работают указатели и рекурсия) — нам влом.
roscomtheend
Да даже через русский язык стремятся перескочить, благо там «собирается» даже с ошибками.
yurrig
Бинарное дерево без указателей, на массиве длиной в 2^n, описано у Кнута, ЕМНИП. Там примерно так: в массиве лежит по индексу 1 корневой элемент, потом оба его дочерние, за ними — 4 их дочерних, и т.д. Нулевой и отсутствующие элементы помечаются как пустые. Для элемента по индексу idx: переход к родителю — idx/2, к левому/правому потомку — 2*idx и 2*idx+1. В каких-то (редких?) случаях так даже может быть экономнее и быстрее, чем на указателях. Disclaimer: я сам всегда на указателях деревья строю, но из песни слова не выкинешь)
khim
То, что вы описали — это то, как реально устроена куча. Тоже интересный вариант, но для обычного дерева — очень плохой, если у вас там дерево не сбалансировано потеряете кучу места. Для реализации очередей с приоритетами — то, что доктор прописал.
yurrig
Вот поэтому я и написал, что в редких случаях. Если дерево заполняется так, что перебалансировка не происходит, оно (почти) полностью заполнено, и используется только для поиска элементов, то поиск может быть довольно быстрым, за счет высокой локальности — промахов процессорного кэша будет поменьше, чем для разбросанного в памяти дерева на указателях.
khim
yurrig
Поясните?
khim
При обработке деревьев, как правило, нужно переходить к левому и правому потомкам, иногда наверх. И крайне редко — к «соседу» слева или справа. А при хранении дерева как вы описали — близко расположены именно «соседи», а полезные ячейки (все три) — очень и очень далеко. На таком дереве разве что «поиск в ширину» хорошо организовывается.
yurrig
Я писал про поиск — там только сверху вниз, и чем ближе элемент к корню, тем чаще по нему бегается; но чем ближе к корню, тем компактнее они размещены. Соответственно, в кэш попадает меньше мусора, и в результате он будет использоваться лучше, чем при размазанной структуре. Но если дерево сильно больше размеров кэша, эффект будет невелик, да.
khim
Это интересное замечание, но, боюсь, неиспользуемые ноды засорят кеш. Хотя, да, было бы неплохо провести исследование. C AVL деревьями даже, может, какой-то выигрыш будет. С красно-чёрными — вряд ли…
Druu
Если надо, то можно сделать под дерево кастомный аллокатор и обеспечить себе любую требуемую локальность, вообще говоря. Так что особо не важно что там и как сорит.
khim
Если бы вы удосужились прочитать про то, что тут обсужадется, то вы бы поняли, что никакой аллокатор ничего не изменит, так как ему всё равно придётся выделать память одним куском по условию задачи.
Druu
Не обязательно большим куском, можно кусками поменьше. Сборщики мусора же как-то работают.
khim
Сборщики мусора работают за счёт дополнительной информации. А если вы вы всё-таки заглянули в начало дискуссии, вместо того, чтобы бредить, то обнаружили бы, что обсуждается хранение дерева, когда ссылки
left
иright
отсутствуют, являются виртуальными…Druu
Вы уже сами запутались. Давайте по порядку:
khim
Собственно вы сейчас повторили ровно то, что я написал. И далее зачем-то предложили в конкретную версию варианта 1 (которому вся эта ветка посвящена) вкручивать аллокатор. Куда его там вкручивать и чем он там поможет — мне лично неясно.
khim
Очень просто. Заводите массив размером с максимальное количество элементов в этом дереве. Вместо указателей — индексы.
На самом деле проще всего начать с такой структуры данных, как «ассоциативный массив» на базе хеш-функции.
Вначале — простой массив пар «ключ/значение», в случае если случаются коллизии… у нас беда.
Потом — начинаем записывать в соседние ячейки… и на какое-то время можно на этом продержаться.
Потом — учимся обходить занятые места и устраивать «дыры», если нам это нужно (тут у нас уже получаются вначале односвязные, а потом и двусвязные списки).
Ну а после этого — можно уже и деревья устроить.
Ryppka
Вы уверены, что 55555 и 55563 — допустимые значения для адреса целого в ILP64 системах?! ;)
khim
На большинстве современных процессоров — да. Но на Alpha, почившей в бозе — нет.
Ryppka
SPARK64? POWER*?
khim
ARM, POWER — в том же месте, что и x86: теоретически можно самому себе строить проблемы и запретить доступ по невыровненным адресам, но пракстически — этот режим никто не использует. Про SPARC не знаю.
Zuy
Хммм, вы меня немного запутали. А кто эти люди кому пытаются обьяснить указатели и они не понимают? Они вообще понимают, как работает процессор?
Получается, что есть какой-то абстрактый человек, который понимает сегменты и смещения в них процессоров Intel, он знает про разницу 32-х и 64-х битной адресации, а когда ему сказали, что '&' берет адрес переменной, а '*' возвращает значение по адресу, он такой:«Нет, вы знаете, не догнал». Странно это как-то.
Интересно, а эти самые люди ссылки в С++ понимают?
robert_ayrapetyan
Ссылки это вообще абстракция над абстракцией, их никто не понимает ). А вообще я не согласен лишь с тем, что сишный указатель — это адрес в памяти. Это довольно избитая и обмусоленная тема, вот тут почитайте.
Zuy
Тут я с вами согласен. Адрес в памяти конечно не точное определение, но покрывающее большинство распространенных архетиктур. Ваше определение формально более точное, но воспринимается тяжелее.
Похожая история и с байтом происходит. Его упрощают до 8-ми бит т.к. количество архитектур где он другого размера крайне мало.
khim
intptr_t
, которая «закрывает тему»: да, теоритечески указатели в C (и C++) могут быть много чем (intptr_t
, строго говоря, опционален), но практически — вы можете просто игнорировать подобные компиляторы.robert_ayrapetyan
Осталось определиться, какую тему закрывает intptr_t, и отсылок к стандарту в той теме как раз предостаточно. Предмет текущего спора, напомню, можно ли утверждать, что указатель (pointer в оригинальном стандарте) — это адрес. А не какие типы данных появились в каком стандарте.
Если как раз почитать стандарты, на которые вы ссылаетесь, то однозначный ответ — нет. Да, численно указатель равен смещению в сегменте данных, но на этом все сходство с offset ptr заканчивается (кстати, даже в оригинальных ассемблерах не употребляют слово адрес, а именно offset — смещение).
В высокоуровневых языках, где вообще нет указателей, не нужно забивать голову низкоуровневой ерундой, но в С извольте называть вещи своими именами и понимать из каких частей и как именно формируется адрес в памяти.
khim
Если мы можем сконевртировать указатель в число, проделать с этим числом всем операции, какие можно производить с числом (в файл, например, записать, или по сетке на другой компьютер послать), то всё, тема закрыта: этот указатель — таки и есть число.
А что такое адрес, извините?
Если вы таки про 8086, то там ещё и сегмент был. И указателей было… много разных:
near
,far
,hure
…Это, в общем-то, нужно только при взаимодействии с ассемблером. Из-за вышеописанного свойства intptr_t указатель — это число. А уж как они связано с тем, что на шину адреса выставиться — дело десятое. Может со сдвигом, может инверсно, может ещё как-нибудь. Но так или иначе — это должно проивходить как-то, иначе указатель свою роль не сможет играть.
P.S. Собственно на это наткнулись эльбрусовцы, когда завели теги в памяти, чтобы указатели перестали быть просто числами и стали чем-то что OS выделяет и на корректность чего, соотвественно, можно положиться. Вот в тот момент, когда выяснилось, что указатель можно превратить в число и потом — обратно в указатель… вся схема и «провисла:»…
robert_ayrapetyan
Ну про число никто не спорит, там вообще все имеет численное представление, даже буквы.
khim
Буквы как раз во всех известных мне языках можно в число и обратно преобразовать. А вот указатели — не во всех. В ISO Pascal или в Ada — нельзя. А потому что «безопасность», а потому что iAPX 432, а потому что указатель — это не просто адрес.
А вот в C (по крайней мере в современном C) — это таки «просто число», то есть «просто адрес».
Ryppka
Может быть, в C и число, и указатель — это просто битовые паттерны с разными правилами интерпретации компилятором: старший бит — знак и т.д. Или 4 бита — номер банка и т.д. Нет?
khim
Дык об этом и речь! В C (по крайней мере при наличии в нём intptr_t) указатель — это просто набор бит, битовый паттерн. Его можно превратить в число и куда-нибудь «послать»… а ведь не на всех машинах это возможно. Забудьте про интерпретаторы, почивший в бозе iAPX32 и даже Эльбрус (хоть вроде как последний таки жив).
Вспомните про .NET! Там указатель — это не просто себе последовательность битов. За ним учёт нужен. Превратить его в число, а потом обратно — низзя. Не положено. А в C/C++ — как раз положено! И всё — в .NET встроить C/C++ нельзя. Недаром Micrososft спецподелие изобрёл. Не потому, что ему больше нечем заняться…
Furax Автор
Это люди, которым в учебнике написали: вот так объявляется массив объектов, вот так — массив указателей на объекты. Вот такой синтаксис обращается к методу объекта по его указателю. Вот так работает оператор new []. Теперь напишите программу, которая будет производить масштабирование сложной геометрической фигуры на плоскости.
В итоге человек, который не до конца понимает, что делает синтаксис, который он использует, начинает писать код, который выглядит как пример, который я дал в самом начале. Подобную ситуацию приходилось видеть не раз и не два, даже если человек честно читал учебник — увы, не все они написаны последовательно, а багаж знаний, накопленных в таких языках, как JavaScript, ещё более усложняет понимание.
khim
Всё равно как если бы математику учили не со сложения и таблицы умножения, а с задач по составлению диффуров и нахождения экстремумов. Понятно, что это полезнее, но без основ — оно всё у вас «провиснет в воздухе» и вы будете делать совершенно идиотские ошибки на ровном месте.
Zuy
А чего же он эти данные с формочки парсит на С/C++ не понимая указатели а не на каком-нибудь Python или, прости господи, PHP?
khim
Проблема не в том, что он парсит формочки на C/C++. Проблема в том, что он считает себя программистом, которому чуть-то нужно подучить C/C++ и всё. Хотя реально — он занимается комбинаторным программированием, дёргая примеры со StackOverflow и комбинирует их случайным образом пока результат не будет походить на то, что требуется.
А потом вот с этим багажом — хотят изучить C/C++. Причём в том же стиле. А он, зараза, понимания требует. Комбинаторным программированием в нём даже программу в 1000 строк не сделать. Вот беда-огорчение…
Druu
Да ладно вам, перекладывание байтиков по когнитивной нагрузке от дерганья формочек ничем существенно не отличается. И на стековерфлоу точно так же есть куча готового кода для разных кейсов.
khim
Если вы «не так» переложите байтики — вам никто худого слова не скажет, просто программа будет вываливаться с невразумительными дампами (да, я знаю, есть много способов во всём этот разобраться — но для этого всё равно нужно какое-то понимание того, что вы делаете).
Druu
Ну, во-первых, можете увидеть и спустя несколько лет после эксплуатации, если проблема в логике.
Во-вторых — для того, чтобы разобраться в невразумительном стектрейсе какого-нибудь эксцепшена из гуевого фреймворка, точно так же требуется понимание того, как все в этом фреймворке работает.
khim
Те, кто могут писать нетривиальную логику — уже и в указателях могут разобраться.
Druu
exception от этого понятным не станет
Так я об этом и сказал с самого начала. Перекладывание формочек от перекладывания байтиков ничем не отличается. И там и там — перекладывание. Сложность возникает тогда, когда процесс перекладывания становится нетривиальным, и не важно, формочки это или байтики. Если же процесс тривиален — то, опять же, разницы нет, формочки у вас или байтики, в обоих случаях можно будет справиться "малыми шевелениями" и прочим стековерфлоу-программированием.
Здесь справедливости ради стоит заметить, что "глубина абстракции" в случае формочек несравнимо выше. То есть — вы можете, в принципе, досконально хорошо понимать, как работает ваш алгоритм, перекладывающий байтики, но не можете досконально хорошо понимать, как работает алгоритм, перекладывающий формочки. Так что потенциальная когнитивная сложность задач с формочками выше. Но только потенциальная.
khim
А вы сюда с каким-то «пониманием» лезете.
Важно. Формочка с тремя полями и база с двумя стобцами — это уже что-то за что можно какую-то денежку, если повезёт получить. А три байта в C++ — этого даже чтобы светодиодом поморгать, скорее всего, не хватит.
Количество переходит в качество и комбинаторное программирование перестаёт работать…
Druu
Мне кажется, то, что в байтоперекладывании за "сложность" платят меньше — факт ортогональный обсуждаемым вопросам.
khim
Нет, это ключевой факт. «Комбинаторным программированием» нельзя написать программу больше определённого размера.
Она просто развалится под собственной тяжестью. И этот размер — особо от языка не зависит.
Но в случае с web-программированием программы подобного размера можно продавать, а в случае с C++ — нет.
А это кардинально меняет всю динамику.
Druu
Ну так еще раз, дело не в том, что байтоперекладывание сложнее, а в том, что за простое байтоперекладывание не платят, в отличии от. Надо все же верно расставлять акценты.
RadicalDreamer
Мне эти «людоедоедоеды» вообще напомнили цитатку с башорга:
Goldseeker
У части студентов понимание этой достаточно простой штуки «щелкает» сразу, а часть неделями не может разобраться и постоянно путается из-за этого. Способ приведенный в статье, конечно, очень странный, но проблема, которую статья ставит — существует.
Duny
В коде ошибка. copy — это массив указателей.
В выражении:
происходит разъименование указателя copy[i], который никуда не указывает. Это UB.
Furax Автор
Об этом и речь.
daiver19
Это все, конечно, занятно понимать just for fun, но на С++ так писать не надо (в принципе, в 99 процентах случаев больше одного указателя не надо).
BalinTomsk
потому что вы на GPU не программировали — я там это добра даже поболе.
Еще с потоками, с другим принципом обхода массивив и т.д.
daiver19
CUDA C — не С++. В статье речь идет о лабе по С++.
Akon32
CUDA вполне себе поддерживает C++. Как минимум лет 7. Может, не полностью, но в документации сейчас язык уже называется "CUDA C++" или "CUDA C/C++".
COCATb
Я просто оставлю это здесь: cdecl.org
TimeCoder
Другой неплохой путь понимания указателей — это посмотреть на их реальный смысл, память и машинную арифметику. Практика! Структуры данных. Вот прямоугольник, там лежит число — это int. Т.е. область памяти, коробочка такая, нули и единицы которой сами по себе мы интерпретируем как число. Теперь нам надо несколько таких чисел сохранить, прямоугольники в памяти идут один за другим — массив. И ещё одна дополнительная переменная, тоже блок памяти маленький, где записан адрес первого элемента массива. Теперь представим, что таких массивов — несколько. Например, их пять. Они разной длины, кстати. Адреса всех не сохранишь в одной переменной. Создаём массив длиной пять, где будут лежать адреса этих массивов. Какой тип элемента в каждом из пяти массивов? Int. Какой тип каждого элемента в массиве верхнего уровня, хранящего адреса тех пяти массивов? Int*. А для работы с этим самым верхним массивом адрес его первого элемента мы запишем в переменную какого типа? Правильно, int**. Если ещё все это на бумажке нарисовать, то будет ещё понятнее, и главное — не надо ломать мозг лингвистическими закорючками, вы видите практическое применение.
P.s. я понимаю, что в it много самоучек. В вузе на курсе С нас год муштровали машинной арифметикой, СДиА, битовыми операциями и пр. Считаю, это надо понимать, а если вы пишите на языке, где есть указатели — то тем более. И если в вузе не отложилось, надо наверстать.
jmdorian
Да, мне в свое время также объясняли, но к пониманию это не приблизило. Посему я решил для себя что пользоваться ими не буду, от греха подальше. Благо и не пришлось потом.
А понимание пришло откуда не ждали — когда в качестве хобби решил заняться программированием под AVR и для этого освоил ассемблер. Все сразу стало куда понятнее. Я не утверждаю что студентам нужно в обязательном порядке вдалбливать ассемблер, но как лирическое отступление при объяснении указателей можно было бы.
Akon32
Примерно так нам и объясняли в вузе. И не припомню проблем с пониманием.
Имхо, объяснять указатели на каких-то мутных лингвистических аналогиях, а не на предельно ясном устройстве абстрактного процессора — очень странная идея.
stychos
Вот кстати интересный факт. Мне и самому намного проще представить указатели как данные в памяти. Но объяснить это посторонним в таком виде зачастую просто невозможно, потому мнение автора может иметь смысл.
Sdima1357
Указатели — зло. Деньги — зло.
Но зла не хватает. Все языки используют указатели, но не все в этом признаются.
WinPooh73
И это ещё тема ссылок на указатели и указателей на ссылки не раскрыта.
С ними было бы что-то вроде:
Не проказничали чтобы
Гомофобофилофобы,
Скоро всех возьмут на вилы
Гомофилофобофилы.
x-foby
В статье поднимается вопрос непонимания начинающими темы указателей и предлагается решать это объяснением синтаксиса (посредством странной аналогии).
Для объяснения темы указателей как таковых существует множество более удачных аналогий, часть из которых приведена комментаторами выше (хотя мне искренне не понятно, что может быть непонятного в определении из условной Викидепии).
Для объяснения же синтаксиса существуют документация, туториалы и т.д.
Объяснять синтаксис технического языка на примере конструкции из живого — это, как минимум, странно и долго. Как максимум — отпугнёт и разочарует.
Но за попытку спасибо)
Free_ze
На практике не так страшны сами указатели, как синтаксис их объявления.
x-foby
Здесь нужно понимать несколько моментов.
С одной стороны, да, всё всегда упирается в синтаксис, поскольку работаем мы [программисты] с кодом; понятие «переменная» и «указатель» справедливы только для кода, непосредственно железо в этих понятиях не нуждается и ими не оперирует.
Поэтому формально я с вами согласен, да.
С другой же стороны, если посмотреть на ситуацию комплексно, то мы сможем свести все причины проблем с указателями всего-то к двум:
Так вот суть в том, что решить первую проблему лингвистическими аналогиями — невозможно. Здесь нужно просто объяснять человеку, что есть память, что есть переменная. Когда
есличеловек разберётся с этим, тогда вопрос сложности восприятия синтаксиса стоять не будет, так как синтаксис не нужно понимать, синтаксис нужно просто выучить. Вне зависимости от языка (я сейчас даже не о технических, не об ЯП — обо всех: русский, английский, etc.) синтаксис — это аспект, который не требует понимания, он требует только зубрёжки.Но здесь мы сталкиваемся со второй проблемой: вы можете зубрить что угодно и сколько угодно, но пока вы не поймёте сути темы — толку ноль.
Именно поэтому у людей и возникают проблемы с такими казалось бы несложными вещами как указатели: они либо путают порядок, в котором должно обучаться, либо выполняют лишь одно из двух действий.
Резюмируя, статья интересная, правда. Для тех, кто итак всё понимает.
Новичкам же от неё толку будет ноль, поскольку она не решает ни одной из вышеперечисленных проблем)
Druu
Это, кстати, да, тому, кто придумал одним и тем же символом (*) обозначать и операцию разыменовывания и спецификатор типа (и вдобавок тот факт, что спецификатор типа принадлежит переменной, а не типу, что откровенная шизофрения), нужно обеспечить пожизненный цик с гвоздями. Да и взятие адреса с побитовым "и" недалеко ушло.
gospodinputin
Я не сформулировал никакого чёткого правила и не сказал про указатели ничего нового.
Спасибо за статью, очень познавательно и очень интересно.
technic93
Если надо дело доходит до ** или не дай
богСтрауструп *** гораздо читабельней сделатьОсобенно удобно если потом это всё еще и в массив кладется, и не надо думать что будет когда операторы
[]
и*
стоят в одном выражении.technic93
А вообще спасибо, весёлая аналогия получилась :)
Ещё можно про const упомянуть:
тоже справа налево раскрывается.
Inine
По инерции начал было соображать, как раскрутить Страуструп***
Nick_Shl
Что бы понять что такое указатели, нужно сначала получить ASM и его разные аресации. Знание, что "нужно положить адрес этой строчки в DX перед вызовом INT 21h" очень помогает. Во всяком случае помогло мне — когда вся группа в универе сидела и втыкала что же такое указатели, мне все были понятно с полуслова. Что интересно, курс ассемблера x86 у нас тоже был, но позже курса Си…
MacIn
Это помогает только для самых примитивных случаев.
«Настоящий» же ад указателей — это когда надо раскручивать по спирали, а не просто справа налево.
ne_zabudka
Жалко Люду. Максимально сократим её возможное присутствие в пищевой цепочке:
людоведаедовед
При условии, что людоведа зовут например Вася, а его жену не Люда.