Это все из-за стека (который является тезкой этого сайта, кстати). Стек - это то, как язык программирования знает, как вернуться туда, где он был после выхода из функции (или метода или подпрограммы и т. Д. c ...). Каждый вызов функции добавляется (помещается в) в стек (вместе с параметрами, передаваемыми в функцию ... здесь это не важно). Стек - это две вещи, это и имя объекта, содержащего данные, и имя класса, который их определяет (что будет важно узнать позже). Значение, добавленное в стек, является указателем на то, откуда был сделан вызов; просто чтобы прояснить, это указывает не на определение функции, а на строку, вызвавшую функцию. Когда программа возвращается из этих функций (поскольку вы использовали return или подразумеваемый возврат прямо перед фигурными скобками), она извлекает стек, чтобы она знала, куда переместить указатель инструкции на следующую.
В вашем примере, foo () помещается в стек 11 раз (в последний раз, когда он просто печатает выходную строку), прежде чем bar () вообще помещается, потому что bar () следует после foo () и foo. () вызывается второй раз (и третий вызов, четвертый вызов и т. д.) перед вызовом первого бара (). Каждый из этих вызовов будет увеличивать и выводить значение, а затем pu sh еще один foo () в стеке. После того, как все функции foo () были вызваны, но до того, как какой-либо из них был удален из стека, функция foo () выполняет каждый pu sh bar () в стеке (это следующая строка после рекурсивной строки), подождите для бара (n) до fini sh, а затем они выходят, что приводит нас к предыдущей рекурсии. Поскольку эти функции foo () были помещены в стек в прямом порядке, и они рекурсивно перед вызывают bar (), они извлекают bar () из стека в обратном порядке (помните, что стек является данными FILO структура). Вот почему он, кажется, ведет обратный отсчет; хотя на самом деле это просто показывает результаты в обратном порядке. Кстати, bar () не обязательно должен существовать, чтобы это работало так: вы могли бы просто добавить cout сразу после рекурсивного вызова foo (), и он бы работал так же.
Каждый раз, когда вы делаете рекурсию, вы должны думать о стеке, потому что он быстро заполняется, и когда это происходит, ваша программа обработает sh. Это cra sh на самом деле хорошая вещь, потому что в противном случае все приложение просто зависнет. Это звучит не намного лучше, но позволяет вам начать жить раньше, так что это здорово. Кроме того, вы, вероятно, обнаружите, что избегать того, что здесь происходит, обычно хорошо, если только вы не хотите, чтобы bar () вызывалась в обратном порядке foo (). В общем, гораздо проще следовать рекурсии, если рекурсия находится в конце рекурсивной функции, когда это возможно.