Чтобы понять, что происходит, вы должны понять, как работает рекурсия. Каждая рекурсивная функция требует условия тестирования для прерывания рекурсии и рекурсивного вызова. У вас есть if (i < 0)
в качестве условия тестирования, а затем у вас два рекурсивных вызова с printf
между ними.
Так как же это работает?
Вы можете думать о рекурсии и сворачивании до тех пор, пока не сработает условие выхода, а затем раскрутиться, когда возвратятся рекурсивные вызовы. Давайте посмотрим, как это работает здесь.
Рекурсия
Когда вы начнете с recur(2)
в main
, по какому пути логика перейдет к условию выхода?
Если вы упростите функцию и сконцентрируетесь на том, что происходит до того, как будет выполнено условие вашего теста, вы получите нечто похожее на:
void recur (int i) {
if (i < 0)
return;
recur (--i);
/* comes after return */
}
Давайте пройдем через это. При первом вызове i = 2
, поэтому --1
pre decriment происходит, делая i = 1
, recur (1)
. Следующий звонок i = 0
, recur (0)
вызывается снова. Наконец, i = -1
, и появляется первый recur(-1)
, поэтому if (i < 0)
выполняется и вызывается return
.
Что теперь? - Разматывать ...
Посмотрите на приведенную выше укороченную функцию. Когда вы return
, вы возвращаетесь с последнего вызова на recur(--i)
, поэтому управление теперь передается следующей команде после первой recur(-1)
(показанной как /* comes after return */
выше). Каковы следующие команды в полной функции?
printf("%d\n", i);
recur (--i);
Каково было значение i
, когда это произошло впервые? (подсказка: -1
).
Итак, printf
называется, и что дальше? Ваш второй recur(--i)
. Там i = -2
, if (i < 0) return;
срабатывает, и вы выходите из второго recur(-2)
- нет команды, которая следует, и этот вызов функции теперь завершен.
Теперь управление переходит к предыдущему вызову, где i
теперь 0
, вызывается printf
, а во второй recur(--i)
вводится i = -1
, который просто снова нажимает return
и вы раскручиваете один На следующем уровне i = 1
возвращается снова после первого вызова recur(--i)
, выводится 1
.
Второй recur(--i)
вызов вводится там, где после декремента i = 0
, и теперь вы снова рекурсируете в первый, где i = -1
снова запускает return
, что заставляет контроллер перейти к printf
с последним -1
печать.
Теперь вы вернетесь ко второму recur(--i)
с i = -2
, вызвав return
и завершив вызов функции и рекурсию.
Итак, если вы вернетесь к управлению своей рекурсивной функцией двумя рекурсивными вызовами и посмотрите на каждый раз, когда достигнуто printf
, вы получите -1, 0, 1, -1
.
Все еще думаете, что рекурсивные функции с несколькими рекурсивными вызовами - это хорошая идея?
(если вы продавец Аспирина - иначе их лучше избегать, если только это не единственный разумный вариант, который у вас есть)
Единственный способ справиться с чем-то подобным - это (1) подружиться с вашим отладчиком и пошагово пройти по коду, сохраняя блокнот, в который был введен вызов, или (2) скопировать и вставить функцию вниз по странице, отслеживая состояние рекурсивного вызова на каждом уровне, и выполняйте его пошагово.
Всегда хороший опыт обучения, но если доступно процедурное решение, логика намного более проста, и вы избегаете затрат на вызов функции при каждом рекурсивном вызове. Рекурсивные функции имеют свое место, и есть некоторые проблемы, которые они обеспечивают очень элегантные решения. Но вы должны помнить, сколько раз будет происходить рекурсия, когда вы выделяете отдельный стек функций и пространство для локальных переменных при каждом вызове, что может привести к StackOverflow.