ВНИМАНИЕ. Эта статья начинается гладко, а затем я врезаюсь в стену! Мне пришлось провести небольшое исследование, чтобы понять, чему меня учили, а затем задокументировать это. Первый элемент, который я использовал, не привел к пониманию, поэтому я продолжал поиск, пока не нашел ресурс, который мне подошёл. Во всяком случае, это продолжается какое-то время.

Перегрузка звучит как то, что вы не должны делать, но перегрузка методов полезна в C#, когда у вас есть несколько методов с одинаковыми именами, но разными параметры. Сначала я этого не понимал, пока не увидел код того, как это должно выглядеть. Моей первой мыслью было: не запутает ли программа несколько методов с одинаковыми именами? Как он узнает, какой из них запустить?

По-видимому, это довольно умно, и, например, вы хотите иметь переменные различных типов данных, но хотите, чтобы они проходили через один и тот же процесс.

static void Print(int a) {
     Console.WriteLine(“Value: “ + a);
}
static void Print(double a) {
     Console.WriteLine(“Value: “ + a);
}

Выше вы видите два метода с одинаковыми именами, но которые могут принимать значения параметров двух разных типов данных.

Кстати, приложение также объясняет знак плюс (+) как способ соединения слова Value: со значением переменной a. Это называется объединением.

Требование к методам перегрузки заключается в том, что они отличаются друг от друга типами и/или количеством параметров. Когда есть несколько методов с одинаковым именем и они вызываются, конкретный метод, который будет использоваться, будет основан на типе данных отправляемых аргументов.

Давайте добавим еще один метод с именем Print, который будет работать вместе с двумя предыдущими:

static void Print(string label, double a) {
     Console.WriteLine(label + a);
}

Далее наш метод Main будет выглядеть так:

static void Main(string[] args) {
     Print(11);
     Print(4.13);
     Print(“Average: “, 7.57);
}

Итак, вот что произойдет:

  • первая строка метода Main вызывает метод Print, который будет соответствовать типу данных отправляемого аргумента. В данном случае это число 11, которое является целым числом. Таким образом, он вызовет первый метод с именем Print(int a) рядом с ним), а затем напечатает Value : 11 на экран.
  • вторая строка метода Main вызывает метод Print, который будет соответствовать типу данных отправляемого аргумента. В данном случае это число 4,13, которое является двойным. Таким образом, он вызовет второй метод с именем Print(двойной a) рядом с ним), а затем напечатает Value : 4.13 на экран.
  • Третья строка метода Main вызывает метод Print, который будет соответствовать типу данных отправляемого аргумента. В этом случае, поскольку есть два аргумента, он вызовет третий метод с именем Print (который имеет (label + a) рядом с ним ). Среднее: будет значение, присвоенное переменной с именем label (в качестве типа данных которой назначена string), и 7,57 будет назначено переменной с именем a (для которой в качестве типа данных назначен double). Затем он выведет на экран Среднее значение: 7,57.

Однако далее в приложении упоминается, что вы не можете перегружать объявления методов, которые отличаются только типом возвращаемого значения. Например, следующие методы приведут к ошибке:

int PrintName(int a) { }
float PrintName(int b) { }
double PrintName(int c) { }

Мне пришлось немного разобраться, чтобы понять, почему это вызовет ошибку. В итоге я нашел следующее объяснение на странице переполнения стека:

Перегруженный метод может иметь или не иметь разные типы возвращаемых значений. Но одного типа возвращаемого значения недостаточно для компилятора, чтобы определить, какой метод должен выполняться во время выполнения.

Это прояснило для меня это, потому что это говорит о том, что с методами, показанными выше, они все принимают целые числа. Таким образом, несмотря на то, что типы возвращаемых данных для каждого из них разные, при вызове метода PrintName невозможно определить, какой из них следует использовать, исходя из типа данных параметра, поскольку все они имеют одинаковую тип данных: целое.

Итак, это перегрузка методов. Наконец, как уже упоминалось, рекурсивный метод — это метод, который вызывает сам себя. Пример, который дает приложение SoloLearn относительно того, когда вам нужно это сделать, — это когда вы вычисляете факториал числа. Они описывают математический термин факториал следующим образом:

…произведение всех положительных целых чисел, которые меньше или равны определенному неотрицательному целому числу.

Факториал обозначается восклицательным знаком (!), например:

4! = 4 * 3 * 2 * 1, что равно 24.

Я хотел немного больше узнать о том, как это работает и почему вы хотите его использовать, и, немного погуглив, оказался на странице на веб-сайте c-sharpcorner.com. Чтобы перейти к концу, вы бы хотели использовать его, потому что это более чистый и эффективный способ написания кода. Например, без рекурсии метод, выполняющий процесс factorial, будет выглядеть так:

Примечание. Прежде чем я покажу это, скажем, в методе Main вы запросили у пользователя число, и это число было присвоено переменной с именем number с типом данных integer. Затем вызывается наш новый метод (называемый Factorial), ему отправляется значение переменной number и результат выполнения этого метода (называемый Factorial ) присваивается новой переменной с именем factorial, тип данных которой двойной.

public static double Factorial(int number) {
     if (number == 0)
     return 1;
     double factorial = 1;
     for (int i = number; i >= 1; i — ) {
          factorial = factorial * i;
     } return factorial;
}

Я попытался просто взглянуть на код, чтобы понять, что происходит, но это не сработало. Итак, допустим, пользователь вводит число 5, вот что происходит:

  • Объявлен метод с именем Factorial, который является общедоступным (что означает, что любой класс может получить к нему доступ), является статическим ( что означает, что он не может создавать какие-либо экземпляры самого себя), вернет значение с типом данных double, и, когда он отправит аргумент, значение этого аргумента будет хранится в переменной с именем число, тип данных которой — целое число.
  • Далее выполняется проверка, равно ли значение переменной number 0. Если оно равно равно 0, то метод возвращает значение 1 в то место, где он был вызван. По-видимому, в «математике» факториал 0 равен 1… и все. Это не кажется логичным, но это так. Если значение number равно не 0, выполняется переход к следующей строке.
  • Создается переменная с именем factorial, которой присваивается тип данных double и значение 1.

Далее идет цикл for, который начинается с числа, введенного пользователем, и умножается на следующее меньшее целое число, а затем на следующее меньшее целое. число, а затем следующее меньшее целое число и так далее, пока оно не уменьшится до 1.

цикл for начинается с создания переменной с именем i, установки ее типа данных на integer и присвоения ее значения, равного значению переменная номер. Он устанавливает условие для проверки, а именно, является ли значение i больше или равно 1. Если это так, он проходит через следующую строку, а затем уменьшает текущее значение i на 1. Если это не так, то текущее значение переменной factorial устанавливается обратно в то место, где оно было вызвано. Продолжим, вспомнив, что пользователь ввел значение 5, которое затем было присвоено переменной с именем number:

  • Вновь созданной целочисленной переменной с именем i присваивается значение переменной с именем число (которое равно 5), поэтому i = 5.
  • Условие, что i больше или равно 1, проверяется, то есть 5 больше или равно 1? И ответ да. Итак, переходим к следующей строке.
  • Переменная factorial с типом данных double теперь устанавливается равной текущему значению переменной factorial, равное 1, умноженное на текущее значение i, равное 5. Итак, факториал переменной равен 5 * 1 или 5. Значение i теперь уменьшено на 1, что означает 5–1, поэтому i теперь равно 4.
  • Условие того, является ли i больше или равным 1, проверяется снова, то есть 4 больше или равно 1? ответ по-прежнему да, поэтому мы переходим к следующей строке.
  • Переменная factorial теперь устанавливается равной текущему значению переменной factorial, которое равно 5, умноженному на на текущее значение i, равное 4. Итак, факториал переменной равен 5 * 4 или 20. Значение i теперь уменьшается на 1, что означает 4–1, поэтому i теперь равно 3.
  • Условие того, является ли i больше или равным 1, проверяется снова, то есть 3 больше или равно 1? ответ по-прежнему да, поэтому мы переходим к следующей строке.
  • Переменная factorial теперь устанавливается равной текущему значению переменной factorial, которое равно 20, умноженному на на текущее значение i, равное 3. Итак, переменная factorial равна 20 * 3 или 60. Значение i теперь уменьшено на 1, что означает 3–1, поэтому i теперь равно 2.
  • Условие того, является ли i больше или равным 1, проверяется снова, то есть 2 больше или равно 1? ответ по-прежнему да, поэтому мы переходим к следующей строке.
  • Переменная factorial теперь устанавливается равной текущему значению переменной factorial, которое равно 60, умноженному на на текущее значение i, равное 2. Таким образом, переменная factorial равна 60 * 2 или 120. Значение i теперь уменьшается на 1, что означает 2–1, поэтому i теперь равно 1.
  • Условие того, является ли i больше или равным 1, проверяется снова, то есть является ли 1 больше или равным 1? ответ по-прежнему да, поэтому мы переходим к следующей строке.
  • Переменная factorial теперь устанавливается равной текущему значению переменной factorial, которое равно 120, умноженному на на текущее значение i, равное 1. Таким образом, переменная factorial равна 120 * 1 или 120. Значение i теперь уменьшено на 1, что означает 1–1, поэтому i теперь равно 0.
  • Условие того, что i больше или равно 1, проверяется снова, то есть 0 больше или равно 1? теперь ответ нет, поэтому мы переходим к следующей строке после цикла for.
  • текущее значение переменной factorial, равное 120, возвращается в точку, где оно было вызвано.

Итак, эта функция берет число, введенное пользователем (5), и выполняет над ним факториал, в результате чего получается 120.

Это, безусловно, было много шагов, и хотя рекурсивный способ написания кода приведет к тому же результату, он написан чище и эффективнее. Тем не менее, давайте посмотрим, как записать математическим способом:

5! = 5*(5–1)*(5–2)*(5–3)*(5–4) = 120

Таким образом, код записывается следующим образом:

if (number == 0)
     return 1;
     return number * Factorial(number-1);

В переводе на английский это означает:

….Хм….

Да я вообще не понимаю, что тут происходит. Я полностью понимаю, как работает factorial, но не могу понять, как код выше его выполняет. Вот что видит мой разум, когда он проходит этот цикл 5 раз:

return number * Factorial(((Factorial(Factorial(Factorial(Factorial-1)-1)-1)-1))-1);

И я думаю, что технически это правильно (я думаю), но как это сделать, не попадая в бесконечный цикл?

…чуть позже…

Погуглив, я нашел на YouTube видео от Jeff Chastine под названием Учебник 17.2 — введение в рекурсию в C#.

Первое, что он делает, — объясняет, как работает factorial, что мы уже делали раньше. Однако, чтобы согласиться с его учебным пособием, вот что он говорит:

  • 75! Является эквивалентом 75 * 74 * 73 * ….* 1

Затем он указывает, что это действительно то же самое, что сказать:

  • 75 * 74!

Следовательно, это то же самое, что сказать:

  • 75 * 74 * 73!

…и так далее, вплоть до 1. Но он также произносит следующую фразу, которая действительно мне понятна:

Чтобы вычислить факториал 75, нам действительно нужно вычислить факториал 74. И чтобы вычислить факториал 74, нам действительно нужно вычислить факториал 73. Когда это остановится? В какой-то момент мы поняли, что 1 факториал — это всего лишь 1.

Затем он перевел это в следующую общую формулу:

n! = n * (n-1)!

Мне также нравятся две вещи, которые он говорит дальше:

  • Собираюсь создать «клоны» функции.
  • Обычно клон имеет меньшую проблему для работы.

Вот как, по его словам, код будет выглядеть на C#:

int factorial (int n) {
     if (n == 1) {
          return 1;
     }
     else {
          return n * factorial(n-1);
     }
}
void Main () {
     int f = factorial(4);
}

Этот код почти точно совпадает с кодом из приложения SoloLearn. Как я делал со своими шагами, вот как он описывает каждую строчку:

  • Для начала тип возвращаемого значения — integer, имя метода — Factorial, и он принимает целое число, которое мы назовем п.
  • Далее мы скажем, что если n равно 1, мы просто вернем значение 1. Теперь ниже мы используем значение 4, но если бы это значение было 1, мы уже знаем, что ответ равен 1, поэтому нет смотреть надо.
  • Если значение n равно не 1, мы вернем результат n (в данном случае это 4), умноженное на рекурсивный вызов факториала n-1.

Затем он завершает код и вставляет несколько строк Console.WriteLines, чтобы мы могли видеть, каково значение n при перемещении по нему:

static int factorial (int n) {
     Console.WriteLine(n);
     if (n == 1) {
          return 1;
     }
     else {
          int result = n * factorial(n-1);
          Console.WriteLine(n);
          return result;
     }
}
void Main () {
     int f = factorial(4);
     Console.WriteLine(f);
}

В этот момент я также решил поместить этот код в Visual Studio, чтобы не просто набирать его здесь наугад, а визуально видеть результаты на YouTube.

Затем, чтобы проследить его, и именно это действительно помогло ему понять, он создал что-то, называемое стеком функций, и говорит:

Идея здесь в том, что мы вызываем функцию, а затем помещаем ее в стек.

  • Итак, в Main мы вызываем метод с именем Factorial и передаем ему значение 4. Затем он поместил это в стек, например:

  • Переходя к функции factorial и зная, что n равно 4, он помещает это в стек , нравится:

  • По мере выполнения он попадает в первую строку Console.WriteLine, говорящую о необходимости вывести на экран текущее значение n, и, следовательно, печатает 4 на экран.
  • Затем он проверяет, равно ли n 1, и, поскольку это не так, переходит к следующей строке.
  • В следующей строке говорится: вычислите результат, который будет равен n * factorial(n-1), что означает, что результат устанавливается равным 4 * factorial(4–1) или 4 * factorial(3).

Вот ключ и причина, по которой мой мозг споткнулся: следующие две строки (Console.WriteLine(n); и return result;) пока не могут быть выполнены, потому что есть незавершенная работа. Итак, на данный момент он поместил его в стек следующим образом и отметил, что есть ожидающие работы:

Другими словами, стек помогает нам отслеживать работу, которую еще нужно выполнить, и в то же время позволяет нам перейти к следующему шагу в коде. Затем он говорит: давайте теперь выясним, что такое факториал числа 3:

  • На данный момент n равно 3.

Раньше это приводило меня в замешательство, потому что я не понимал, как из-за цикла n стало равным 3. Что теперь ясно, так это то, что цикл действительно не приводил к тому, что n равнялся 3. На данный момент мы просто пытаемся вычислить factorial для 3 с помощью этого: factorial(3). Это означает: вызвать метод factorial и отправить ему значение 3. Итак, он обновляет стек, вот так:

  • Для этого клона в этот момент мы переходим к началу метода с именем factorial и нажимаем Console.WriteLine(n), или Console.WriteLine(3) и вывести 3 на экран.
  • Затем он проверяет, равно ли n 1, и, поскольку это не так, переходит к следующей строке.
  • В следующей строке говорится: вычислите результат, который будет равен n * factorial(n-1), что означает, что результат равен 3 * factorial(3–1) или 3 * factorial(2).

Опять же, в этот момент, поскольку у этого клона все еще есть незавершенная работа, он обновляет стек:

На данный момент мы просто пытаемся вычислить factorial для 2 с помощью этого: factorial(2), поэтому он обновляет куча:

  • Для этого клона в этой точке мы переходим к началу метода с именем factorial и нажимаем Console.WriteLine(n ) или Console.WriteLine(2) и выведите 2 на экран.
  • Затем он проверяет, равно ли n 1, и, поскольку это не так, переходит к следующей строке.
  • В следующей строке говорится: вычислите результат, который будет равен n * factorial(n-1), что означает, что результат равен 2 * factorial(2–1) или 2 * factorial(2).

Еще раз, поскольку у этого клона еще есть незавершенная работа, он обновляет стек:

На данный момент мы просто пытаемся вычислить factorial для 1 с помощью этого: factorial(1), поэтому он обновляет куча:

  • Для этого клона в этот момент мы переходим к началу метода с именем factorial и нажимаем Console.WriteLine(n) или Console.WriteLine( 1) и выводит 1 на экран.
  • Затем он проверяет, равно ли n 1 и, поскольку 1равно равно 1, код требует вернуть значение 1, поэтому он обновляет стек следующим образом:

На этом этапе мы, по его словам, завершились, и, говоря его словами, стек начинает схлопываться до Main. Поскольку из стека мы знаем, что factorial(1) равен 1, мы можем выполнить замену, и произойдет следующее:

Теперь этот прогон кода может перейти к следующей строке, которой является Console.WriteLine(n); Помните, что в этот момент n равно 2, поэтому строка кода становится Console.WriteLine(2);, которая выводит 2 на экран. Затем мы можем сделать следующую замену, которая вызовет следующее:

Теперь этот прогон кода может перейти к следующей строке, которой является Console.WriteLine(n); Помните, в этот момент n равно 3, поэтому строка кода становится Console.WriteLine(3);, которая выводит на экран 3. Затем мы можем выполнить подстановку next, что приведет к следующему:

Теперь этот прогон кода может перейти к следующей строке, которой является Console.WriteLine(n); Помните, что в этот момент n равно 4, поэтому строка кода становится Console.WriteLine(4);, которая выводит 4 на экран. Затем мы можем выполнить окончательную замену, которая приведет к следующему:

Теперь строка return result; отправит окончательный результат, 24, туда, где был сделан первоначальный вызов, который был методом Main. , который изначально отправил значение 4. Теперь мы знаем, что это дает 24. Затем он переходит к следующей строке, которой является Console.WriteLine(f); (и помните, что первоначальный вызов метода factorial из Main em> устанавливает в качестве результата новую переменную с именем f, имеющую тип данных integer). Поскольку f равно 24, строка кода становится Console.WriteLine(24);, что выводит 24 на экран.

Мы закончили!

Я понимаю, как работает рекурсия, и ключом к ней была идея о том, что нормально иметь ожидающую работу. Что всю работу не нужно делать… ПРЯМО СЕЙЧАС!

Надеюсь, вы застряли в этом путешествии. Как всегда, пожалуйста, дайте мне знать, если я что-то не так, и если что-то нуждается в дополнительных пояснениях.

Кроме того, вот ссылка на предыдущую записьИзучение кода — часть 5b: аргументы.