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