Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
лабораторные / Лаб.раб.5С#.doc
Скачиваний:
95
Добавлен:
21.03.2015
Размер:
210.43 Кб
Скачать

Функции с побочным эффектом

Функцияназываетсяфункцией с побочным эффектом, если помимо результата, вычисляемогофункциейи возвращаемого ей в оператореreturn, она имеетвыходные аргументыс ключевыми словамиrefиout. В языках C/C++функции с побочным эффектомприменяются сплошь и рядом. Хороший стиль ОО-программиро-вания не рекомендует использование такихфункций. Выражения, использующиефункции с побочным эффектом, могут потерять свои прекрасные свойства, присущие им в математике. Еслиf(a)-функция с побочным эффектом, тоa+f(a)может быть не равноf(a) +a, так что теряется коммутативность операции сложения.

Вот тест, демонстрирующий потерю коммутативности сложения при работе с этой функцией:

/// тестирование побочного эффекта

public void TestSideEffect() {

int a = 0, b=0, c=0;

a =1; b = a + f(ref a);

a =1; c = f(ref a)+ a;

Console.WriteLine("a={0}, b={1}, c={2}",a,b,c);

}

Результаты работы этого метода:

a=2,b=2,c=3;

Обратите внимание на полезность указания ключевого слова refв момент вызова. Его появление хоть как-то оправдывает некоммутативность сложения.

Оператор return

Одним из операторов, относящимся к группе операторов перехода, является операторreturn, позволяющий завершить выполнение процедуры или функции. Его синтаксис:

return [выражение];

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

Рекурсия

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

Определение (рекурсивного метода): методP(процедура или функция) называетсярекурсивным, если при выполнении тела метода происходит вызов методаP.

Рекурсияможет быть прямой, если вызовPпроисходит непосредственно в теле методаP.Рекурсияможет быть косвенной, если в телеPвызывается методQ(эта цепочка может быть продолжена), в теле которого вызывается методP. Определения методовPиQвзаимно рекурсивны, если в теле методаQвызывается методP, вызывающий, в свою очередь, методQ.

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

public long factorial(int n)

{

if (n<=1) return(1);

else return(n*factorial(n-1));

}//factorial

Функция factorialявляется примером прямого рекурсивного определения - в ее теле она сама себя вызывает. Здесь, как и положено, есть нерекурсивная ветвь, завершающая вычисления, когдаnстановится равным единице. Это пример так называемой "хвостовой"рекурсии, когда в теле встречается ровно один рекурсивный вызов, стоящий в конце соответствующего выражения. Хвостовуюрекурсиюнамного проще записать в виде обычного цикла. Вот циклическое определение той же функции:

public long fact(int n)

{

long res =1;

for(int i = 2; i <=n; i++) res*=i;

return(res);

}//fact

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

public void TestTailRec()

{

Hanoi han = new Hanoi(5);

long time1, time2;

long f=0;

time1 = getTimeInMilliseconds();

for(int i = 1; i <1000000; i++)f =han.fact(15);

time2 =getTimeInMilliseconds();

Console.WriteLine(" f= {0}, " + "Время работы

циклической процедуры: {1}",f,time2 -time1);

time1 = getTimeInMilliseconds();

for(int i = 1; i <1000000; i++)f =han.factorial(15);

time2 =getTimeInMilliseconds();

Console.WriteLine(" f= {0}, " + "Время работы

рекурсивной процедуры: {1}",f,time2 -time1);

}

Каждая из функций вызывается в цикле, работающем 1000000 раз. До начала цикла и после его окончания вычисляется текущее время. Разность этих времен и дает оценку времени работы функций. Обе функции вычисляют факториал числа 15.

Соседние файлы в папке лабораторные