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

 

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

Структура функции

Попробуем написать функцию, которая будет возводить значение в заданную степень, то есть вычислять результат умножения значения х на себя n раз, что в математике записывается как хп.

// Листинг 27

// Функция для вычисления х в степени n, где n больше или равно 0

double power(double х, int n) // Заголовок функции

{ // Тело функции начинается здесь...

double result = 1.0; // Здесь сохраняется результат

for (int i = 1; i <= n; i++)

result *= x;

return result;

} // ...и заканчивается здесь

Заголовок функции

Сначала рассмотрим в этом примере заголовок функции. Далее строка - первая строка функции:

double power(double х, int n) // Заголовок функции


Он состоит из трех частей, которые описаны ниже.

Тип возвращаемого значения (в данном случае - double).

Имя функции (в данном случае - power).

Параметры функции, заключенные в скобки (в данном случае- х и n, типа double и int соответственно).

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

Наша функция имеет два параметра: х - значение типа double, которое нужно возвести в степень, и n - значение степени типа int. Эти переменные параметры участвуют в вычислениях, выполняемых функцией, вместе с другой переменной - result, объявленной в ее теле. Имена параметров и любые переменные, определен­ные в теле функции являются локальными по отношению к ней.

В конце заголовка функции и после закрывающей фигурной скобки ее тела точка с запятой не требуется.

Общая форма заголовка функции

Общая форма заголовка функции может быть записана следующим образом:

тип_возврата имя_функции (список_параметров)

 

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

void my_function(void)

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

void my_function ()

Функция с типом возврата void не должна использоваться в составе выражений в вызывающей программе.

Тело функции

Все необходимые вычисления функции выполняются операторами в ее теле, которое следует за заголовком. Тело функции из нашего последнего примера начинается с объявления переменной result, инициализированной значением 1.0. Переменная result локальна по отношению к функции, как и все автоматические переменные, объявленные в ее теле. Это значит, что переменная result прекращает существование после того, как функция завершит работу. Возникнет вопрос: если переменная result прекращает существование по завершении работы функции, то как ее значение может быть возвращено? Когда функция завершает свою работу, автоматически создается копия значения, подлежащего возврату, и эта копия возвращается в программу.

Вычисление производится в цикле for. Управляющая переменная цикла i объявлена в самом цикле, и предполагается, что она последовательно принимает значения от 1 до n. Переменная result умножается на х при каждой итерации цикла, и это происходит n раз, чтобы сгенерировать необходимое значение. Если n равно 0,то оператор цикла не будет выполнен ни разу, поскольку условное выражение сразу возвратит false, и result останется равным 1.0.

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

Область видимости переменных, объявленных внутри функции, определяется таким же образом, как уже упоминалось. Переменная создается в точке ее объявления и прекращает свое существование в конце блока, в котором была объявлена. Однако существует разновидность переменных, которая составляет исключение из этого правила - переменные, объявленные как static. Оператор return

Оператор return возвращает значение result в точку вызова функции. Общая форма оператора return такова:

return выражение;

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

Если тип возврата функции специфицирован как void, то за оператором return не должно следовать никакого выражения. Оно должна записываться очень просто:

return;

Использование функций

В точке, где в программе используется функция, компилятор должен знать кое-что о ней, чтобы скомпилировать ее вызов. Ему нужна достаточная информация, чтобы идентифицировать функцию, и убедиться, что вы применяете ее правильно. И если только функция, которую вы намерены использовать, не появилась где-то ранее в том же исходном файле, вы должны объявить функцию с помощью оператора, который называется прототипом функции.


 

Прототипы функций

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

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

Для примера функции power () вы можете написать следующий прототип:

double power(double value, int index);

He следует забывать о точке с запятой в конце прототипа функции! Без этого вы получите от компилятора сообщение об ошибке.

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

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

При желании вы можете вообще пропустить имена параметров в прототипе функции и просто написать так:

double power(double, int);

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

Использование функции


 

// Листинг 28

//Объявление, определение и применение функции

#include <iostream.h>

double power(double x, int n);//Прототип функции

 

void main ()

{

int index = 3;

double x = 3.0;

double y = 0.0;

y = power(5.0,3);//Передача констант в виде аргументов

cout <<endl << “5.0 v kube = “ <<y ;

cout <<endl

<<“3.0 v kube =“

<<power(3.0, index); //Вывод возвращенного значения

x = power (x, power(2.0, 2.0));//применение функции в качестве //аргумента с автоматич. привед. 2-го параметра

cout << endl << “x =“ <<x;

cout <<endl;

}

//Функция для вычисления x в степени n, где n больше или равно 0

 

duble power(double x, int n) //Заголовок функции

{ //Тело функции начинается здесь…

double result = 1.0;

for(int i = 1; i<=n; i++)

result *=x;

return result;

}//… и заканчивается здесь

 

После обычного оператора #include для поддержки ввода-вывода идет прототип функции power (). Без прототипа компилятор не сможет обработать вызовы функции в main (), а выдаст вместо этого серию сообщений об ошибках.

В этом примере использовано ключевое слово void в функции main (), где обычно появляется список параметров, чтобы показать, что параметры не применяются. Ключевое слово void также может применяться в качестве типа возврата функции, чтобы показать, что функция не возвращает значения. Если вы определяете тип возврата как void, то не должны помещать никакого значения рядом с оператором return внутри функции; в противном случае вы получите от компилятора сообщение об ошибке.

Чтобы использовать функцию power () для вычисления 5,03 и сохранить результат в переменной у в нашем примере используется следующий оператор:

у = power (5.0, 3);

 

Здесь значения 5.0 и 3 - аргументы функции. В данном случае они являются константами, но вы можете использовать любые выражения в качестве аргументов, если они дают результат требуемого типа.

Аргументы функции power () подставляются вместо параметров х и n, которые используются в определении функции. Вычисление производится с применением этих значений, а копия результата, 125, возвращается вызывающей функции main (), где присваивается переменной у. Вы можете думать о функции как о значении в операторе или выражении, где она появляется.

Затем в примере значение переменной у выводится на экран:

cout <<endl<< “5.0 v kube =“ <<y;

Далее вызов функции применяется прямо в составе оператора вывода:

cout <<endl

<<“3.0 v kube =“

<<power(3.0, index); //Вывод возвращенного значения

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

После этого функция power () используется еще раз в операторе:

х = power(х, power(2.0, 2.0)); // Использование функции, как аргумента

В этом случае функция power () вызывается дважды. Первый вызов - правый в выражении, и его результат служит вторым аргументом для второго, левого вызова. Хотя оба аргумента в подвыражении power (2 . 0, 2.0) являются литералами типа double, на самом деле функция вызывается с первым аргументом 2 . 0 и вторым - целочисленным литералом 2. Компилятор преобразует значение double, приведенное в качестве второго аргумента, к типу int, поскольку знает на основании прототипа, что типом второго аргумента должен быть int:

double power(double x, int n); // Прототип функции

Результат 4.0 типа double возвращается первым вызовом функции power() после преобразования к типу int значение 4 передается в качестве второго аргумента следующему вызову функции, где первый аргумент - х. Поскольку х имеет значение 3.0, значение вычисляется 3,04 и результат, равный 81.0, присваивается х.

Этот оператор заключает в себе два неявных преобразования типа double в тип int, вставку которых обеспечивает компилятор. При таком преобразовании данных возможна потеря данных, поэтому компилятор издает предупреждающие

сообщения, когда такое случается, хотя он сам и вставил это преобразование. Обычно полагаться на автоматическое преобразование типов, потенциально чреватое потерей данных - опасная практика в программировании, и совсем не очевидно вытекает из кода, что такое преобразование было намеренным. Гораздо лучше, когда необходимо, явно указывать в коде преобразование типа, используя

для этого операцию static_cast. То есть последний оператор в этом примере лучше переписать так:

х = power (х, static_cast<int>(power(2.0, 2) ) ) ;

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

 

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

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

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

 

Дана действительная матрица размера 8xn. Найти с использованием функций значение наибольшего по модулю элемента матрицы, а также индексы какого-нибудь элемента с найденным значением модуля.

// Листинг 29

#include <stdlib.h>

#include <time.h>

#include <math.h>

#include <iostream.h>

#include <iomanip.h>

Прототипы функций

Выделение памяти

void allocMemory(int m, int n, double **&matrix);