Функции

Пройдя не такой уж долгий путь от нашего первого урока к этому, вы «подошли» к изучению функций в C++. Функции — это именованный фрагмент кода, который повторяется в программе 2 или больше раз . Когда мы пишем функцию, необходимо дать ей имя и в дальнейшем, чтобы её вызвать в программе (из main() или из другой функции), надо обратиться к ней по этому имени.

Мы уже встречали функции в предыдущих уроках. Это функции для строк (символьных массивов) strlen()strcmp(), функция для генерации случайных чисел rand(). Мы их применяли в программах и, например, передавали в функцию strlen() строку, а она нам возвращала количество символов в этой строке (целое число). Это конечно происходило не волшебным образом, а функция принимала нашу строку, обрабатывала её и возвращала нам значение, которое подсчитала. То есть кто-то до нас написал этот самый код функции, которая считает длину строки и мы успешно ею пользуемся в своих программах. И эта функция здорово экономит наше время, сокращает количество строк кода и облегчает его читаемость.

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

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

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

  • до main-функции;
  • после main-функции. В этом случае необходимо до main-функции объявить прототип собственной функции.

В этой статье и следующих мы будем пользоваться вторым способом, так как он является более распространённым. Первый способ можно использовать, если функция одна и её код совсем небольшой. Пока мы пишем простые программы, такое встречается часто. Но для программ посложней, будем писать несколько функций которые будут состоять не из 2-3 строк, а побольше. Покажу вам как выглядит определение функции до main():

#include <iostream>

using namespace std;

 

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

{

cout << "Сайт purecodecpp.com\n";

cout << "Изучаем функции в C++\n";

cout << "Эта функция выводит на экран три строки\n\n";

}

 

int main()

{

setlocale(LC_ALL, "rus");

 

cout << "Вызов ourFunctionForPrint() из main()\n\n";

 

ourFunctionForPrint();

 

cout << "Функция ourFunctionForPrint() отработала!\n";

cout << "Далее продолжает работу main()\n\n";

 

return 0;

}

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

#include <iostream>

using namespace std;

 

void ourFunctionForPrint(); // прототип

 

int main()

{

setlocale(LC_ALL, "rus");

 

cout << "Вызов ourFunctionForPrint() из main()\n\n";

 

ourFunctionForPrint();

 

cout << "Функция ourFunctionForPrint() отработала!\n";

cout << "Далее продолжает работу main()\n\n";

 

return 0;

}

 

void ourFunctionForPrint() // определение

{

cout << "Сайт purecodecpp.com\n";

cout << "Изучаем функции в C++\n";

cout << "Эта функция выводит на экран три строки\n\n";

}

Прототип функции размещен в строке 4, а её определение находится в самом низу программы в строках 20 — 25. Что касается выполнения программы: сначала компилятор прочтет прототип. Это даст ему знать о том, что где-то после main() располагается определение этой функции. Далее начнется выполнение главной функции main(). Выполняться она будет, пока компилятор не встретит имя функции ourFunctionForPrint(). Тогда он найдет определение этой функции, которое расположено после main(), по имени, указанному в прототипе, выполнит её код, после чего снова вернется к выполнению команд main-функции.

В итоге на экране увидим:

Поговорим об определении функций.

Функции в C++ могут не возвращать никаких значений (как в примере) и могут возвращать какое-либо значение. Если функция не возвращает ничего, то это функция типа void.

Синтаксис функции, которая не возвращает значений:

Имя функции следует давать придерживаясь правил для имен переменных. Единственное — желательно чтобы оно содержало глагол, так как функция выполняет действие. Например если она считает среднее арифметическое можно дать название calculateAverage, если выводит что-то на экран — showText. Имя должно говорить за себя, чтобы не пришлось оставлять лишние комментарии в коде.

Параметры (или аргументы функции) — это данные, которые функция принимает и обрабатывает в теле. Если функции не нужно ничего принимать для обработки, круглые скобки оставляют пустыми. Согласно правилам High Integrity C++ Coding Standard желательно не определять функции с большим количеством параметров (больше 6).

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

Принимает один параметр:

#include <iostream>

using namespace std;

 

void printQuestion(int questionCount);

 

int main()

{

setlocale(LC_ALL, "rus");

 

printQuestion(7);

cout << endl << endl;

 

return 0;

}

 

void printQuestion(int questionCount)

{

for (int i = 0; i < questionCount; i++)

{

cout << '?';

}

}

В 10-й строке кода функция получает параметр — целое число 7. С ним (с этим числом) произойдет то, что описано в определении функции — строки 16 — 22. А именно — это число подставится в заголовок цикла for. Выражение i < questionCount станет равнозначным i < 7 . В итоге мы увидим на экране 7 знаков вопроса.

Принимает три параметра:

#include <iostream>

using namespace std;

 

void printOurSymbol(int symbCount, int lineCount, char ourSymbol);

 

int main()

{

setlocale(LC_ALL, "rus");

 

printOurSymbol(7, 5, '@');

cout << endl << endl;

 

return 0;

}

 

void printOurSymbol(int symbCount, int lineCount, char ourSymbol)

{

for (int i = 0; i < lineCount; i++)

{

for (int j = 0; j < symbCount; j++)

{

cout << ourSymbol;

}

cout << endl;

}

}

Синтаксис функции, которая возвращает значение:

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

#include <iostream>

using namespace std;

 

int calculateSomeDigits(int d1, int d2, char ch);

 

int main()

{

setlocale(LC_ALL, "rus");

 

int digit1 = 0;

int digit2 = 0;

int res = 0;

char chVar = 0;

 

cout << "1-е число: ";

cin >> digit1;

cout << "2-е число: ";

cin >> digit2;

cout << "Операция(+ или -): ";

cin >> chVar;

 

res = calculateSomeDigits(digit1, digit2, chVar);

cout << "res = " << res << endl;

 

return 0;

}

 

int calculateSomeDigits(int d1, int d2, char ch)

{

if (ch == '+')

return d1 + d2;

if (ch == '-')

return d1 - d2;

}

Определение функции располагается в строках 28 — 34. Если пользователь введет +, сработает блок if в строке 30, а в нём соответственно сработает return d1 + d2; . После этого код функции уже не будет обрабатываться дальше. Компилятор вернется к выполнению main-функции.

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

Еще немного о прототипе: прочитав его до main, компилятор получает сведения о том, какой тип возвращаемого значения будет у функции (или она вообще не возвращает значения — имеет тип void) и о том, какие параметры будут в неё переданы, в каком количестве и в какой последовательности.

Прототип int calculateSomeDigits(int d1, int d2, char ch); говорит компилятору, что функция вернет на место её вызова целое число и о том, что при вызове в нее должно быть передано два целых числа и один символ. При вызове функции, мы должны передать ей столько параметров сколько указано в её заголовке при определении. Передавать параметры необходимо в том же порядке, как они определены в круглых скобках за именем функции. Иначе возникнут ошибки при компиляции либо программа будет работать некорректно.

Синтаксис прототипа функции:

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

Имена переменных-параметров в прототипе можно не указывать. Следующий прототип равнозначен тому, что выше.

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