Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык программирования Сpp 25.09.11 (2).doc
Скачиваний:
16
Добавлен:
19.08.2019
Размер:
10.09 Mб
Скачать

2. Функции

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

Функция это поименованный участок памяти выполняющий заданные инструкции с объявленными аргумнтами.

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

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

double myFunction1(int i, double d, bool b, char ch){

тело функции

return возвращаемое_значение;}

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

Если функция не возвращает никакого значения, то это указывается словом void.

void myFunction2(int i1, int i2, char c){

тело функции

}

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

myFunction1(2, 1.04, true, ‘W’);

Число параметров в операторе вызова функции должно соответствовать числу параметров в ее описании. Последовательность типов в операторе вызова и описании должна так же соответствовать. Вообще говоря, список параметров может быть пустым. И с такой функцией мы уже встречались. Это функция с именем main(). Это главная функция любой программы. Именно с нее, точнее с ее первого оператора начинается выполнение программы. Правда, внимательный читатель может возразить: при описании этой функции не указывался тип возвращаемого значения, слова void тоже не было. Действительно это так. На самом деле функция выглядит несколько сложнее, о чем мы еще будем говорить. Сейчас только заметим, что тип возвращаемого значения функции main() это int или void. В С++ для этой функции сделано исключение, которое состоит в том, что тип возвращаемого значения можно не писать. При этом большинство компиляторов выводят предупреждение, о том, что эта функция должна возвращать значение.

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

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

Тип_функции Имя_функции([тип 1-го формального параметра [и его имя]] [,тип 2-го формального параметра [и его имя]] [,и т.д.])

Например:

int numberDay(char ch); /*функция возвращает целое число, аргументом явяляется переменная типа char с именем ch */

double summa_bank(double summa, int manth); /* это функция возвращает значение типа double, она имеет два аргумента причем имена указаны в прототипе */

char name_day(int number); /*функция одного аргумента типа int

возвращаемое значение типа char */

void print(int char); /* функция не возвращает никакого значении,

на что указывает слово void - пустота */

int NUMBER(int); /* в прототипе достаточно указать число

и тип переменных, имена переменных называть необязательно*/

void Zummer5( ); /*функция может не иметь аргументов*/

void Zummer5(void); /*тоже самое */

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

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

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

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

Тип_функции Имя_функции([тип 1-го формального параметра и его имя][,тип 2-го формального параметра и его имя] [,и т.д.]

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

return (имя или выражение);//это значение присвоится имени функции

}

Например,

int numberDay(char ch)

{

Тело функции

return (//число или выражение необязательно в скобках);

}

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

Вероятно читателю не очень ясны обобщенные рассуждения. Поэтому перейдем к примеру.

//Прототип которая выполняет сложение двух целых чисел

int summa(int x, int y);

//описание функции которая выполняет сложение двух целых чисел

int summa(int x, int y) /*объявляется тип возвращаемого значения,

* имя функции тип и имена аргументов */

{

z=x+y; //код программы

return z; //возвращаемая величина – z должна иметь объявленный тип

}

Если теперь в программе встретится оператор cout<<summa(1,2); то еа экране появится число 3.

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

//Программа калькулятор

#include <iostream.h>

//сначала описываются прототипы функций

//вот они

double summa(double arg1, double arg2);

double subtraction(double arg1, double arg2);

double multiply(double arg1, double);

double division(double, double);

/* В данном случае используются 4 функции.

Каждая выполняет одну арифметическую операцию.

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

//=======================================================

main(){

double x,y,result;

char symbol;

while(true){ //бесконечный цикл для работы калькулятора

cout <<"\n";

cin >>x>>symbol>>y;

switch (symbol){

case '+': result= summa(x,y); break;

case '-': result= subtruction(x,y); break;

case '*': result= multiply(x,y); break;

case '/': result= division(x,y); break;

default: cout << "\nThe program is over";

}

cout<<"\n"<< result;

}

}

//==========================================================

/*После окончания главной части программы дадим описание самих функций подобно тому как происходит описание переменных*/

//+++++++++++++++++++++++++++++++++++++++++++++++++++++

double summa(double argument1, double argument2)

{//начало тела функции

double consequence; // consequence значит результат

consequence=argument1+argument2;

return consequence;

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

}

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++

//описание следующей функции

//-------------------------------------------------------

double subtraction(double arg2, double arg1)

{

return arg2-arg1; /* из первого аргумента вычитается второй */

}

//-----------------------------------------------------

//*****************************************************

double multiply(double a, double b)

{

return (a*b); /* обратите внимание, что можно

возвращать не число, а результат */

}

//*****************************************************

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

double division(double x,double y)

{

if (y!=0)return x/y;

else return 1.0e300; /* оператор return может встречаться

в функции несколько раз*/

}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

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

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

void temperature(int t){

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

return; //этот оператор можно не записывать

}

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

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

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

int main(int argc, char* argv[])

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

return 0;

}

Вы видите, что в данном случае функция main() имеет два аргумента, один это целое типа int – число аргументов, другой символьный char, даже не просто символьный а целый массив с некоторой звездочкой. Возврат функции main() значения 0 свидетельствует об успешном завершении программы. В наших примерах это никак не используется, хотя в серьезных проектах это свойство функции применяется довольно часто. Может возникнуть вопрос о том кому функция main() возвращает значение? Ответ простой – операционной системе. Заметим, что в наших задачах операционная система никак не реагирует на полученное сообщение.

По поводу аргументов функции main() мы подробно поговорим когда познакомися с другим типом данных, который называются указателями. Сейчас только заметим, что, на самом деле, функция main(int argc, char* argv[])) может иметь сколько угодно аргументов, или не иметь ни одного. Хотя в скобкак вроде бы стоит всего два аргумента. Действительное количество аргументов задается следующим образом. Первый аргумент, стоящий в скобках функции main() и обозначенный как argc, говорит о количестве этих аргументов, его значение не может быть меньше 1, а второй, argv[], об их адресах, которые записываются в специальную таблицу, которая называется массивом. Впрочем, о массивах мы тоже будем говорить позднее.

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

//рекурсивная функция факториал

#include <iostream.h>

int factorial(int n);

main(){

int n,result;

while(true){

cout <<"\n" <<"n=";

cin>> n;

cout<<factorial(n);

}

}

int factorial(int n){

if(n==1)return 1;

else return (n*factorial(n-1));

}

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

О рекурсии мы еще будем говорить при выполнении практических задач, а сейчас обратим наше внимание на то, что С++ позволяет присвоить значения аргументов функций по умолчанию. И хотя все слова в этом предложении находятся на своих местах, смысл его не совсем понятен. Поэтому поясним сказанное на примере. Предположим, что для какой либо программы мы используем функцию, которая по дате определяет день недели. Какая дата чаще всего встречается в повседневной жизни? Ответ простой – текущая, т.е. сегодняшний день. Поэтому естественно, что такая функция, если дата не указана, должна говорить какой сегодня день. Значение, которое принимает переменная, если её величина заранее не указана, называется значением по умолчанию. Так вот аргументы функций могут иметь значения по умолчанию.

Аргумент или аргументы по умолчанию задаются присваиванием его значения при объявлении. Например:

void function(int n=5, int m=10);

Теперь вызывая функцию function можно передать ей целочисленные параметры function(5,10), а можно не передавать function(). В этом случае n будет равно 5, а m равно 10.

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

void function1(int n, int m=10);

void function2(int n, int m=10, double p=3.1415926);

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

void function3(double p=3.1415926, int n, int m=10,);

то это вызовет ошибку. Это вполне естественно, т.к. к функции function2 можно обратиться указав значение только первого аргумента function2(5). К функции function3 следовало бы обращаться иначе function2(,5,). Запятые без аргументов являются дополнительным источником ошибок и, поэтому, в С++ такой порядок следования аргументов запрещен.

Кажется невероятным то, что в языке С++ можно объявить функцию с неопределенным числом параметров. Точнее с некоторым количеством заданных параметров плюс неопределенное число параметров. Необычно то, что для каждой функции нужно отвести некоторый объем памяти, а в данном случае его как будто определить нельзя. Хотя с другой стороны, такими функциями мы уже пользовались при вводе-выводе. Объявление таких функций мало отличается от тех, которые мы рассмотрели ранее. Синтаксис практически тот же. Сначала записывается прототип

тип имя_функции (список_аргументов, …);

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

Например, прототип функции с именем func, которая имеет два обязательных параметра и сколько угодно дополнительных (или вовсе не иметь их) выглядит так

int func(int x, int y, …);

Этот способ определения параметров используется и при определении функции.

Для работы с неопределенным списком в заголовочгом файле stdarg.h определен вспомогательный тип va_list и три макроса va_start, va_arg и va_end.

Макрос va_start имеет синтаксис:

void va_start(va_list ap, lastfix)

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

Макрос va_arg имеет синтаксис:

type va_arg(va_list ap, type)

Макрос возвращает значение очередного аргумента из списка. Параметр type указывает тип аргумента. Перед вызовом va_arg значение ap должно быть установлено вызовом va_start или va_arg. Каждый вызов va_arg переводит указатель ap на следующий аргумент.

Макрос va_end имеет синтаксис

va_end(va_list up)

Макрос завершает работу со списком, освобождая память. Он должен вызываться после того как прочтется весь список аргументов.

Рассмотрим пример в котором вызывается функция которая рассчитывает сумму m первых натуральных чисел меньших 6.

#include <iostream>

#include <stdarg.h> // подключние файла

sum(int n,...); // объявление прототипа функции sum

using namespace std;

//---------------------------------------------------

main(){

int m;

cin>>m;

cout<< sum(m,1,2,3,4,5); // вызов функции суммирования m чисел

}

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

sum(int m,...){

double s=0;

va_list ap; // ap – переменная для записи адреса

va_start(ap, m); //Определяется адрес первого аргумента списка

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

s+=va_arg(ap,int); //добавляется очередной аргумент из списка

va_end(ap); // освобождается память

return s;

}

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