Функции

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

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

type name ( parameter1, parameter2, ...) { statements }

type
name
parametersint x), и фактически действует внутри функции как регулярная переменная, которая является локальной для функции. Назначение параметров – разрешить передачу аргументов функции из того места, откуда она вызывается.
– statementsэто тело функции. Это блок утверждений, окруженных фигурными скобками {}, которые определяют, что фактически делает функция.

Давайте посмотрим на пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function example
#include <iostream>
using namespace std;

int addition (int a, int b)
{
  int r;
  r=a+b;
  return r;
}

int main ()
{
  int z;
  z = addition (5,3);
  cout << "The result is " << z;
}
Результат - 8

Эта программа разделена на две функции: additionи main. Помните, что независимо от порядка, в котором они определены, программа C ++ всегда начинается с вызова main. Фактически, mainэто единственная функция, которая называется автоматически, а код в любой другой функции выполняется только в том случае, если ее функция вызывается из main(прямо или косвенно).

В приведенном выше примере mainначинается с объявления переменной zтипа int, и сразу после этого он выполняет первый вызов функции: он вызывает addition. Вызов функции следует структуре, очень похожей на ее объявление. В приведенном выше примере вызов additionможно сравнить с его определением только несколькими строками раньше:


Параметры в объявлении функции имеют четкое соответствие аргументам, переданным в вызове функции. Вызов передает два значения, 5а 3функция -; они соответствуют параметрам aи b, объявляются для функции addition.

В тот момент, когда функция вызывается из main, управление передается функции addition: здесь выполнение mainостановлено и возобновляется только после завершения additionфункции. В момент вызова функции значение обоих аргументов ( 5и 3) копируется в локальные переменные int aи int bвнутри функции.

Затем внутри addition, объявляется другая локальная переменная ( int r) и посредством выраженияr=a+b, результат aплюса bприсваивается r; который для этого случая, где a5 и bравен 3, означает, что ему присваивается значение 8 r.

Заключительное утверждение внутри функции:

return r;

Ends additionи возвращает элемент управления обратно в точку, где была вызвана функция; в этом случае: функционировать main. В этот момент программа возобновляет свой курс при mainвозвращении точно в тот же момент, когда он был прерван вызовом addition. Но дополнительно, поскольку additionимеет тип возврата, вызов оценивается как имеющий значение, и это значение является значением, указанным в операторе return, который закончился addition: в данном конкретном случае значение локальной переменной r, которое на момент returnимеет значение 8.


Следовательно, вызов additionявляется выражением со значением, возвращаемым функцией, и в этом случае этому значению 8 присваивается значениеz, Это как если бы весь вызов функции ( addition(5,3)) был заменен возвращаемым значением (т. Е. 8).

Затем main просто печатает это значение, вызывая:

cout << "The result is " << z;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// function example
#include <iostream>
using namespace std;

int subtraction (int a, int b)
{
  int r;
  r=a-b;
  return r;
}

int main ()
{
  int x=5, y=3, z;
  z = subtraction (7,2);
  cout << "The first result is " << z << '\n';
  cout << "The second result is " << subtraction (7,2) << '\n';
  cout << "The third result is " << subtraction (x,y) << '\n';
  z= 4 + subtraction (x,y);
  cout << "The fourth result is " << z << '\n';
}
Первый результат - 5
Второй результат - 5
Третий результат: 2
Четвертый результат - 6

Подобно additionфункции в предыдущем примере, этот пример определяет subtractфункцию, которая просто возвращает разницу между двумя ее параметрами. На этот раз mainэта функция вызывает несколько раз, демонстрируя более возможные способы вызова функции.

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

1
2
z = subtraction (7,2);
cout << "The first result is " << z;

Если мы заменим вызов функции на возвращаемое им значение (т. Е. 5), у нас будет:

1
2
z = 5;
cout << "The first result is " << z;

С той же процедурой мы могли бы интерпретировать:

cout << "The second result is " << subtraction (7,2);

в виде:

cout << "The second result is " << 5;

так как 5 – это значение, возвращаемое subtraction (7,2).

В случае:

cout << "The third result is " << subtraction (x,y);

Аргументы, переданные для вычитания, являются переменными, а не литералами. Это также справедливо и прекрасно работает. Функция вызывается со значениями xи yимеет в момент вызова: 5 и 3 соответственно, возвращая 2 в качестве результата.

Четвертый вызов снова аналогичен:

z = 4 + subtraction (x,y);

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

z = subtraction (x,y) + 4;

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

1
2
z = 4 + 2;    // same as z = 4 + subtraction (x,y);
z = 2 + 4;    // same as z = subtraction (x,y) + 4; 

Функции без типа. Использование пустоты

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

type name ( argument1, argument2 ...) { statements }

void

1
2
3
4
5
6
7
8
9
10
11
12
13
// void function example
#include <iostream>
using namespace std;

void printmessage ()
{
  cout << "I'm a function!";
}

int main ()
{
  printmessage ();
}
Я - функция!

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

1
2
3
4
void printmessage (void)
{
  cout << "I'm a function!";
}

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

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

printmessage ();

Круглые скобки – это то, что отличает функции от других видов объявлений или заявлений. Следующее не будет вызывать функцию:

printmessage;

Возвращаемое значение основного

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

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

return 0;

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

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

стоимость описание
0 Программа прошла успешно
EXIT_SUCCESS Программа была успешной (такая же, как и выше).
Это значение определено в заголовке <cstdlib>.
EXIT_FAILURE Программа не выполнена.
Это значение определено в заголовке <cstdlib>.

Поскольку неявный return 0;оператор for mainявляется сложным исключением, некоторые авторы считают хорошей практикой явно писать инструкцию.

 

Аргументы, переданные по значению и по ссылке

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

1
2
int x=5, y=3, z;
z = addition ( x, y );

В этом случае добавление функции передается 5 и 3, которые являются копиями значений xи y, соответственно. Эти значения (5 и 3) используются для инициализации переменных, заданных в качестве параметров в определении функции, но любая модификация этих переменных внутри функции не влияет на значения переменных x и y вне нее, поскольку x и y были сами не перешли к функции на вызов, а только копии своих значений в этот момент.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// passing parameters by reference
#include <iostream>
using namespace std;

void duplicate (int& a, int& b, int& c)
{
  a*=2;
  b*=2;
  c*=2;
}

int main ()
{
  int x=1, y=3, z=7;
  duplicate (x, y, z);
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}
x = 2, y = 6, z = 14

Чтобы получить доступ к своим аргументам, функция объявляет свои параметры в качестве ссылок . В C ++ ссылки обозначаются амперсандом ( &), следующим за типом параметра, как и в параметрах, взятых duplicateв примере выше.

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

В самом деле, abи cстать псевдонимы аргументов , передаваемых при вызове функции ( xyиz), и любое изменение aвнутри функции фактически изменяет переменную xвне функции. Любое изменение на bмодифицирует y, и любое изменение на cмодифицирует z. Поэтому , когда в примере, функция duplicateизменяет значения переменных abи c, значения xyи zбудут затронуты.

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

void duplicate (int& a, int& b, int& c) 

Было ли это определено без знаков амперсанда как:

void duplicate (int a, int b, int c)

Переменные не будут передаваться по ссылке , а по значению , создавая вместо этого копии своих значений. В этом случае выходной сигнал программы был бы значения xyи zне будучи изменен (например, 1, 3 и 7).

Соображения эффективности и константные ссылки

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

1
2
3
4
string concatenate (string a, string b)
{
  return a+b;
}

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

Но эту копию можно вообще избежать, если оба параметра сделаны ссылками :

1
2
3
4
string concatenate (string& a, string& b)
{
  return a+b;
}

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

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

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

1
2
3
4
string concatenate (const string& a, const string& b)
{
  return a+b;
}

Квалифицируя их как const, функция запрещается изменять значения ни и aне b, но и фактически получить доступ к их значениям в качестве ссылок (алиасы аргументов), не делая фактических копий строк.

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

Встроенные функции

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

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

Например, приведенная выше функция конкатенации может быть объявлена ​​как строка:

1
2
3
4
inline string concatenate (const string& a, const string& b)
{
  return a+b;
}

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

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

Значения по умолчанию в параметрах

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// default values in functions
#include <iostream>
using namespace std;

int divide (int a, int b=2)
{
  int r;
  r=a/b;
  return (r);
}

int main ()
{
  cout << divide (12) << '\n';
  cout << divide (20,4) << '\n';
  return 0;
}
6
5

В этом примере есть два вызова функции divide. В первом:

divide (12)

Вызов только передает один аргумент функции, хотя функция имеет два параметра. В этом случае функция принимает второй параметр равным 2 (обратите внимание на определение функции, которое объявляет второй параметр as int b=2). Следовательно, результат равен 6.

Во втором вызове:

divide (20,4)

Вызов передает два аргумента функции. Поэтому значение по умолчанию для bint b=2) игнорируется и bпринимает значение, переданное как аргумент, то есть 4, что дает результат 5.

Объявление функций

В C ++ идентификаторы могут использоваться только в выражениях после их объявления. Например, некоторая переменная xне может использоваться перед объявлением с помощью инструкции, например:

int x;

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

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

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

Список параметров не обязательно должен содержать имена параметров, а только их типы. Тем не менее имена параметров могут быть указаны, но они являются необязательными и не обязательно должны совпадать с именами в определении функции. Например, функция, вызываемая protofunctionс двумя параметрами int, может быть объявлена ​​с помощью любого из этих операторов:

1
2
int protofunction (int first, int second);
int protofunction (int, int);

В любом случае, включая имя для каждого параметра, всегда улучшается читаемость декларации.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// declaring functions prototypes
#include <iostream>
using namespace std;

void odd (int x);
void even (int x);

int main()
{
  int i;
  do {
    cout << "Please, enter number (0 to exit): ";
    cin >> i;
    odd (i);
  } while (i!=0);
  return 0;
}

void odd (int x)
{
  if ((x%2)!=0) cout << "It is odd.\n";
  else even (x);
}

void even (int x)
{
  if ((x%2)==0) cout << "It is even.\n";
  else odd (x);
}
Введите номер (0 для выхода): 9
Это странно.
Введите номер (0 для выхода): 6
Это даже.
Введите номер (0 для выхода): 1030
Это даже.
Введите номер (0 для выхода): 0
Это даже.

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

Следующие строки:

1
2
void odd (int a);
void even (int a); 

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

Но объявление функций до их определения не только полезно для реорганизации порядка функций внутри кода. В некоторых случаях, например, в данном конкретном случае, по меньшей мере , одной из деклараций требуется, так как oddи evenвзаимно называется; есть вызов evenв oddи вызов oddвeven, И, следовательно, нет способа структурировать код так, чтобы oddон был определен раньше evenи evenраньше odd.

Рекурсии

Рекурсивность – это свойство, которое функции должны вызывать сами по себе. Это полезно для некоторых задач, таких как элементы сортировки или вычисления факториала чисел. Например, чтобы получить факториал числа ( n!), математическая формула будет:

n! = n * (n-1) * (n-2) * (n-3) ... * 1
Более конкретно 5!(факторный из 5) будет:

5! = 5 * 4 * 3 * 2 * 1 = 120
И рекурсивной функцией для вычисления этого в C ++ может быть:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// factorial calculator
#include <iostream>
using namespace std;

long factorial (long a)
{
  if (a > 1)
   return (a * factorial (a-1));
  else
   return 1;
}

int main ()
{
  long number = 9;
  cout << number << "! = " << factorial (number);
  return 0;
}
9! = 362880

Обратите внимание, что в функции factorial мы включили вызов самому себе, но только если переданный аргумент был больше 1, так как в противном случае функция выполняла бесконечный рекурсивный цикл, в котором, когда он достигнет 0, он будет продолжать умножаться на все отрицательные числа (вероятно, провоцирующие переполнение стека в какой-то момент во время выполнения).

Залишити відповідь