Лабораторная работа 3. Программа с функциями
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

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

 

Теоретические сведения

Функции в языке С++

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

Даже если в программе нет повторяющихся блоков, которые обязательно оформляются функцией, функции все равно использовать следует. Функции нужны для упрощения структуры программы. Разбив задачу на подзадачи и оформив каждую из них в виде функции, мы поступаем по принципу, известному с древних времен: “Разделяй и властвуй!”

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

 

а) Объявление и определение функции

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

Функцию можно объявить, не определяя ее полностью, с помощью ее прототипа.

тип имя_функции (тип имя_парам1,…, тип имя_парам N);

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

Примеры

void My_func (int Par1, float Par2, double Par3);  или

void My_func (int, float, double);

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

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

В примерах типом функции является тип void (неопределенный). Этот тип данных был введен для функций, для которых нельзя сказать, что результатом работы функции является целое число или вещественное число и так далее. Такие функции выполняют набор операций, совершают какие-то действия, но не призваны решить какую-то конкретную задачу, например, вычислить факториал. В других языках программирования, например, в Паскале, нет функций с неопределенным типом, для подобных целей служат такие подпрограммы как процедуры. Функции типа void не возвращают в вызывающую программу никакого результата, им просто нечего возвращать.

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

Формат определения функции:

тип имя_функции (тип имя_парам1,…, тип имя_парам N)

{

// тело функции

}

Пример

#include <iostream>

void CntUp (void);        // объявление прототипов функций

void CntDown (void);

 

int main( )

{ CntUp( );               // использование функций в программе, то есть

CntDown( );          // вызов функций

return 0;

}

void CntUp(void)     // определение функции CntUp

{ int i;

cout << “Считаем до 10” << endl;

for (i = 1; i <= 10; i++)

   cout << i << “ “;

cout << endl;

}

void CntDown(void)     // определение функции CntDown

{ int i;

cout << “Считаем от 10 обратно” << endl;

for (i = 10; i >= 1; i--)

   cout << i << “ “;

cout << endl;

}

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

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

 

б) Параметры функции

Формальными называются параметры функции, находящиеся в скобках, при объявлении функции с помощью прототипа и при ее определении, а фактическими – параметры, подставляемые на место формальных при вызове функции.

Пример.

#include <iostream>

int max(int a, int b);      // прототип функции поиска максимального из двух     

                                      // целочисленных параметров

void main ( )

{ int m, k, l;

cout << “m=”;

cin >> m;

cout << “k=”;

cin >> k;

l = max (m, k);

cout << “max =” << l << endl;

}

int max(int a, int b)

{ if (a > b) return a;

else return b;

}

Здесь формальным параметрам a и b соответствуют фактические параметры m и k.

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

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

Пример

 void Func_1(сп_параметров)

{ if (условие)

return;

операторы;

return;

}

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

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

Пример

 void Func_2(сп_параметров)

{ if (условие)

return 0;

операторы;

return 1;

}

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

Пример

Из двух заданных значений выбрать большее или меньшее по запросу пользователя.

 

#include <iostream>

int MaxMin (int p, int x, int y); // прототип функции       

void main ( )

{ int pr, s1, s2, res;

cout << “Введите признак (0 – поиск min, 1 – поиск max)”;

cin >> pr;

cout << “Введите сравниваемые значения”;

cin >> s1 >> s2;

res = MaxMin (pr, s1, s2);

cout << “res =” << res;

}

// полное описание функции MaxMin

int MaxMin (int p, int x, int y)

{ int m_m;

if (p == 0)

   { if (x < y) m_m = x;

        else

           { if (x > y) m_m = x;

                else m_m = y;

            }

  return m_m;

}

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

Пример

double Cube (double r);  // прототип

int main ( )

{ double x, y = 3.1415926;

x = Cube(y);

return 0;

}

double Cube (double r)  // описание

{ return r * r * r;

}

Лучше всего, если типы фактических параметров соответствуют типам формальных параметров. Значения совместимых типов (например, при передаче аргумента типа int параметру типа float) расширяются автоматически, но может произойти искажение информации из-за ошибок округления. При передаче аргумента типа float параметру типа int выдается предупреждение, но преобразование допустимо, хотя и с явной потерей информации. В случае несовместимых типов компилятор фиксирует ошибку.

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

Пример.

Даны отрезки a, b, c. Для каждой тройки этих отрезков, из которых можно построить треугольник, определить площадь этого треугольника.

 

#include <iostream>

#include <сmath.h>

float square (float x, float y, float z)

{ float p;

if ((x + y > z) && (y + z > y) && (x + z > y))

   { p = (x + y + z) / 2;

      return sqrt(p * (p – x) * (p – y) * (p – z));

   }

    else

      { cout << “Нельзя построить треугольник с такими сторонами” << endl;

         return 0;

       }

}

void main ( )

{ float a, b, c, d;

   cout << “Введите значения сторон”;

   cin >>a >> b >> c >> d;

   cout << “a=” << a << “b=” << b << “c=” << c << “S=” << square(a, b, c) <<endl;

   cout << “a=” << a << “b=” << b << “d=” << d << “S=” << square(a, b, d) <<endl;

   cout << “a=” << a << “c=” << c << “d=” << d << “S=” << square(a, c, d) <<endl;

   cout << “b=” << b << “c=” << c << “d=” << d << “S=” << square(b, c, d) <<endl;

}

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

 

в) Локальные и глобальные переменные

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

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

Допустим, имеем в программе функцию с прототипом

int Stepen (int a, int n); // возведение в целую степень целого числа

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

c = Stepen(2, 5);

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

- выделится в стеке память для локальных переменных функции;

- формальным параметрам присвоятся значения фактических параметров:

( a = 2; n = 5 );

- выполнится программа функции, то есть два будет возведено в пятую степень;

- полученное значение функции будет передано в место обращения к этой функции (в так называемую точку возврата), то есть присвоится значение переменной c и осуществится переход к выполнению следующих действий основной функции программы main( )

- .

 

 


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

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

- большее по сравнению с доступной памятью суммарное пространство, занимаемое всеми локальными переменными,

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

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

Локальными являются и переменные, объявленные внутри функции main, а глобальные – объявлены вне всех функций, в том числе и вне функции main.

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

 

г) Передача параметров по ссылке

До сих пор мы рассматривали примеры программ, в которых были организованы пользовательские функции, в которых параметры передавались по значению. В примере со степенной функцией Stepen (a, n) в стек копировались значения параметров, они участвовали в работе функции, сами не изменялись. После завершения выполнения кода функции копии параметров уничтожались (память, выделяемая в стеке для функции, отдается в переиспользование).

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

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

Пример

#include <iostream>

int incr (int m)

{ m = m + 1;

return m;

}       

int main ( )

{ int n = 5;

cout << “n = ”<< incr(n) << endl;

cout << “n = ” << n << endl;

return 0;

}

Функция incr() в качестве значения возвращает целочисленную величину, на единицу превышающую значение параметра функции.

Вывод результатов выполнения программы на экран будет выглядеть так:

n = 6

n = 5

Поскольку функция incr() возвращает значение, на единицу большее аргумента (параметра), результат этого выражения равен 6. Но, поскольку параметр передается по значению, при выполнении инструкции incr(n) для переменной n автоматически создается копия, которая и передается параметром функции incr(). В соответствии с кодом функции значение переменной-копии увеличивается на единицу и полученное значение возвращается в качестве результата функции. Как только результат функцией возвращен, переменная-копия прекращает свое существование. Поэтому функция вычисляется корректно, а переменная-параметр не меняется!

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

#include <iostream>

// параметр передается по ссылке

int incr (int &m)

{ m = m + 1;

return m;

}       

int main ( )

{ int n = 5;

cout << “n = ”<< incr(n) << endl;

cout << “n = ” << n << endl;

return 0;

}

Результат выполнения программы на экран будет выглядеть так:

n = 6

n = 6

Так как параметр функции передается по ссылке, то все манипуляции в теле функции выполняются не с копией параметра, а непосредственно с параметром. Таким образом, вызов функции incr(n) не только возвращает в качестве результата увеличенное на единицу значение аргумента, но и приводит к тому, что этот аргумент действительно увеличивается на единицу!

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

 

Порядок выполнения работы

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

 

Контрольные вопросы

1. Что такое функция?

2. С какими целями используют в программах пользовательские функции?

3. Чем формальные параметры отличаются от фактических?

4. Что такое ссылка?

5. Когда параметры функции передаются по ссылке?

 

Содержание отчета

1. Титульный лист отчета

2. Запись алгоритма решения задачи

3. Текст программы на языке С++

4. Скриншоты результатов, выводимых на экран для заранее подготовленных тестовых примеров

 


Дата: 2019-03-05, просмотров: 179.