Указатели C как лингвистический парадокс

в 20:19, , рубрики: C, 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++ быстрее понять эту тему. Если принять звёздочку за еду, а Люду — за базовый тип… Вы поняли.

Автор: Furax

Источник

* - обязательные к заполнению поля