Функции. Часть 1. Описание функции. Фактические и формальные параметры. Передача параметров в функцию по значению и по адресу. Прототип функции

Функции. Часть 1. Описание функции. Фактические и формальные параметры. Передача параметров в функцию по значению и по адресу. Прототип функции


Содержание



1. Что такое функция? Определение функции. Преимущества использования функций

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

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

Функция – это часть программы, которая имеет следующие свойства или признаки:

  • есть логически самостоятельной частью программы;
  • имеет имя, на основании которого осуществляется вызов функции (выполнение функции). Имя функции должно соответствовать правилам создания идентификаторов языка C++;
  • может содержать список параметров, которые передаются ей для обработки или использования. Если функция не содержит списка параметров, то такая функция называется функцией без параметров;
  • может возвращать (не обязательно) некоторое значение. В случае, если функция не возвращает никакого значения, тогда указывается ключевое слово void;
  • имеет собственный программный код, который расположен между фигурными скобками { }. Программный код решает задачу, поставленную на эту функцию. Программный код функции, реализованный в фигурных скобках, называется «тело функции».

Использование функций в программах дает следующие преимущества:

  • компактная организация программы путем удобного вызова программного кода по его имени, который в программе может встречаться несколько раз (повторяться);
  • экономия памяти, размера исходного и исполнительного кода и т.д.;
  • уменьшение риска возникновения ошибок для больших наборов кодов;
  • повышение читабельности программного кода.

2. Какая общая форма описания функции?

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

тип имя_функции(список_параметров или void)
{
    тело_функции
    [return] (выражение);
}

где

  • тип – тип значения, возвращаемого функцией. Если поле «тип» содержит ключевое слово void, то функция не возвращает никакого значения;
  • имя_функции – это непосредственно имя функции. По этому имени осуществляется вызов программного кода, реализованного в теле_функции. Кроме того, имя_функции есть указателем на эту функцию. Значением указателя есть адрес точки входа в функцию;
  • список_параметров – параметры, которые передаются в функцию. Функция может получать любое число параметров. Если описывается функция без параметров, то в скобках указывается ключевое слово void;
  • тело_функции – набор операторов программного кода, реализующих алгоритм вычисления внутри функции;
  • return (выражение) – ключевое слово return указывает, что функция возвращает значение заданное в (выражение). Слово return может встречаться в нескольких местах тела функции в зависимости от алгоритма (повторяться).

3. Примеры описания и использования функций, которые не возвращают значения

Если функция не возвращает значения, тогда она должна начинаться из ключевого слова void.

Пример 1. Функция MyFunc1() без параметров, которая не возвращает значения.

Если в теле некоторого класса или модуля описать функцию:

// описание функции, которая не получает и не возвращает параметров
void MyFunc1(void)
{
    // тело функции - вывод текста на форму в элемент управления label1
    label1->Text = "MyFunc1() is called";
    return; // возврат из функции
}

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

...

// вызов функции из другого программного кода (например, обработчика события)
MyFunc1();

...

Пример 2. Функция MyFuncMult2(), которая получает один параметр целого типа и не возвращает значения. Функция осуществляет умножение полученного параметра на 2 и выводит результат на форму в элементе управления label1.

// функция, которая получает целое число, умножает его на 2 и выводит результат
void MyFuncMult2(int x)
{
    int res;     // внутренняя переменная
    res = x * 2; // вычисление результата
    label1->Text = res.ToString(); // вывод результата на форму
    return;
}

Вызов функции из другого программного кода:

// вызов функции из обработчика события
MyFuncMult2(42); // будет выведен 84
MyFuncMult2(-8); // -16

Пример 3. Функция, которая получает 2 параметра типа double, находит их произведение и выводит его на форму в элементе управления label1.

// функция, которая умножает параметр x на параметр y и выводит результат на форму
void MyFuncMultDouble(double x, double y)
{
    double z;
    z = x*y;
    label1->Text = z.ToString();
    return;
}

Вызов функции из другого программного кода:

...

MyFuncMultDouble(2.5, -2.0); // будет выведено -5
MyFuncMultDouble(1.85, -2.23); // будет выведено -4.1255

...

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

Пример 1. Функция, которая получает один параметр целого типа, умножает его на 5 и возвращает результат. Функция не выполняет вывода результата.

// функция, которая умножает параметр на 5
int Mult5(int d)
{
    int res;
    res = d * 5;
    return res; // возврат результата
}

Вызов функции из другого программного кода

...

// вызов функции из другого программного кода
int x, y;
x = 20;
y = Mult5(x);   // y = 100
y = Mult5(-15); // y = -75

...

Пример 2. Функция вычисления значения y = sign(x), определяющегося по правилу:

Реализация функции:

int sign(float x)
{
    if (x<0) return -1;
    if (x==0) return 0;
    if (x>0) return 1;
}

Вызов функции из другого программного кода:

...

int res;

res = sign(-0.399f); // res = -1
res = sign(0.00f);   // res = 0
res = sign(2.39);    // res = 1

...

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

Пример 1. Пример функции MaxFloat(), получающей 2 параметра типа float и возвращающей максимум из них.

// функция, которая находит максимум между двумя вещественными числами
float MaxFloat(float x, float y)
{
    if (x>y) return x;
    else return y;
}

Следует обратить внимание, что в данной функции 2 раза встречается оператор return.

Вызов функции MaxFloat() из другого программного кода:

// вызов функции из другого программного кода
float Max; // переменная - результат
Max = MaxFloat(29.65f, (float)30); // Max = 30.0

double x = 10.99;
double y = 10.999;
Max = MaxFloat(x, y); // Max = 10.999
Max = MaxFloat((float)x, (float)y); // Max = 10.999 - так надежнее

Пример 2. Функция MaxInt3(), которая находит максимальное значение между тремя целыми числами.

// функция, которая находит максимум между тремя целыми числами
// функция получает 3 целочисленных параметра с именами a, b, c
int MaxInt3(int a, int b, int c)
{
    int max;
    max = a;
    if (max<b) max = b;
    if (max<c) max = c;
    return max;
}

Вызов функции из другого программного кода

// вызов функции из другого программного кода
int a = 8, b = 5, c = -10;
int res;

res = MaxInt3(a, b, c);       // res = 8
res = MaxInt3(a, b+10, c+15); // res = 15
res = MaxInt3(11, 2, 18);     // res = 18

6. Какие существуют способы передачи параметров в функцию? Пример

В C++ существует 3 способа передачи параметров в функцию:

  • передача параметра по значению (Call-By-Value). Это простая передача копий переменных в функцию. В этом случае изменение значений параметров в теле функции не изменит значений, которые передавались в функцию извне (при ее вызове);
  • передача параметра по адресу переменной. В этом случае функции в качестве параметров передаются не копии переменных, а копии адресов переменных, то есть указатель на переменную. Используя этот указатель функция осуществляет доступ к нужным ячейкам памяти где расположена передаваемая переменная и может изменять ее значение. Общие сведения об указателях приведены здесь;
  • передача параметра по ссылке (Call-By-Reference). Передается ссылка (указатель) на объект (переменную), что позволяет синтаксически использовать эту ссылку как указатель, и как значение. Изменения, внесенные в параметр, который передается по ссылке, изменяют исходную копию параметра вызывающей функции.

Пример. Этот пример демонстрирует отличие между передачей параметров по значению, по адресу и по ссылке. Описывается функция, которая получает три параметра. Первый параметр (x) передается по значению. Второй параметр (y) передается по адресу (как указатель). Третий параметр (z) передается по ссылке.

// функция MyFunction
// параметр x - передается по значению (параметр-значение)
// параметр y - передается по адресу
// параметр z - передается по ссылке
void MyFunction(int x, int* y, int& c)
{
    x = 8;   // параметр изменяется только в границах тела функции
    *y = 8; // параметр изменяется и за пределами функции
    z = 8; // параметр изменяется и за пределами функции
    return;
}

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

int a, b, c;
a = b = c = 5;

// вызов функции MyFunction()
// параметр a передается по значению a->x
// параметр b передается по адресу   b->y
// параметр c передается по ссылке   c->z
MyFunction(a, &b, c); // на выходе a = 5; b = 8; c = 8;

Как видно из результата, значение переменной a не изменилось. Переменная a передавалась в функцию MyFunction() с передачей значения (первый параметр).

Однако, значение переменной b после вызова функции MyFunction() изменилось. Это связано с тем, что в функцию MyFunction() передавалось значение адреса переменной b. Имея адрес переменной b в памяти компьютера, внутри функции MyFunction() можно изменять значения этой переменной с помощью указателя y.

Точно так же изменилось и значение c после вызова функции. Ссылка есть адрес объекта в памяти. С помощью этого адреса можно иметь доступ к значению объекта.

7. Что такое формальные и фактические параметры функции? Пример

Формальные параметры – это переменные, принимающие значение аргументов (параметров) функции. Если функция имеет несколько аргументов (параметров), их тип и имена разделяются запятой ‘ , ‘.

При вызове функции с параметрами, компилятор осуществляет копирование копий формальных параметров в стек.

Пример. Функция MyAbs(), находящая модуль числа имеет один формальный параметр x.

// функция, которая находит модуль вещественного числа
float MyAbs(float x) // x - формальный параметр
{
    if (x<0) return (float)(-x);
    else return x;
}

Вызов функции из другого программного кода (другой функции)

// вызов функции из другого программного кода
float res, a;

a = -18.25f;
res = MyAbs(a); // res = 18.25f; переменная a - фактический параметр
res = MyAbs(-23); // res = 23; константа 23 - фактический параметр

При вызове функции из другого программного кода имеет место фактический параметр. В данном примере фактический параметр это переменная a и константа 23.

При вызове функции фактические параметры копируются в специальные ячейки памяти в стеке (стек – часть памяти). Эти ячейки памяти отведены для формальных параметров. Таким образом, формальные параметры (через использование стека) получают значение фактических параметров.

Поскольку, фактические параметры копируются в стек, то изменение значений формальных параметров в теле функции не изменит значений фактических параметров (так как это есть копия фактических параметров).

8. Какая область видимости формальных параметров функции? Пример

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

Пример. Функция, которая вычисляет факториал целого числа n.

// функция, которая вычисляет n!
unsigned long int MyFact(int n) // начало области видимости формального параметра n
{
    int i;
    unsigned long int f = 1; // результат
    for (i=1; i<=n; i++)
        f = f*i;
    return f;          // конец области видимости формального параметра n
}

Вызов функции из другого программного кода (другой функции):

// вызов функции из другого программного кода
int k;
unsigned long int fact;
k = 6;
fact = MyFact(k); // fact = 6! = 720

9. Что такое прототип функции? Пример

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

Если отсутствует прототип функции, то компилятор принимает тип формальных параметров равным типу фактических параметров. Это может привести к ошибке.

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

Пример. Пусть дана функция Div(), которая получает 2 числа типа long int и возвращает результат деления нацело этих чисел. Функция не имеет прототипа (см. дальше).

// функция, которая делит 2 числа нацело
long int Div(long int x, long int y)
{
    long int res;
    res = x / y; // результат - целое число
    return res;
}

Если в другом программном коде написать:

...

// вызов функции из другого программного кода
int a, b, c;

a = 290488;
b = -223;
c = Div(a, b); // правильный ответ: c = -1302. Значение c может быть ошибочным

...

то есть риск возникновения ошибочного результата в переменной c. Это связано с тем, что при вызове функции компилятор не имеет информации о типе формальных параметров (x, y), которые используются в функции. Компилятор считает, что тип параметров в функции такой же как и тип фактических параметров (переменные a, b), то есть тип int. Однако, функция Div() использует значение параметров типа long int, который в памяти имеет большую разрядность чем тип int. Поэтому, может возникнуть искажение значений.

Во избежание такой ошибки рекомендуется давать прототип функции. В данном случае прототип имеет вид:

long int Div(long int, long int);

В прототипе указывается:

  • тип значения, возвращаемого функцией;
  • имя функции;
  • типы аргументов (параметров) функции.

Если функция описывается в классе и вызовется из методов этого класса, тогда подобных ошибок не будет. Это связано с тем, что в классе прототип функции известен всем методам класса.


Связанные темы