Урок 7: «Разделяй и властвуй»
Этот урок, дорогие друзья, придется вам изучить вдоль и поперек. Если вы не поймете его, то дальнейшее изучение языка просто бессмысленно. Кроме того, при изучении ООП у вас возникнет ряд трудностей.
Тема сегодняшнего урока – функции. Функция – это подпрограмма, которую может вызвать основная программа или другая функция. Что такое подпрограмма?
А это, уважаемые читатели, отдельная единица программы, выполняющая определенную задачу. Фактически, все программы, с которыми мы раньше сталкивались, носили чисто линейный характер. Да, мы использовали в задачах и условия, и циклы. Но код был сильно загроможден подобными конструкциями. Кроме того, поиск логической ошибки программы сильно затруднен в подобном коде. Кстати, тут же сразу могу сказать – старайтесь, чтобы размер основного кода вмещался только на одной странице вашего экрана. А избыточный код оформляйте либо в подпрограммы, либо в классы.
Так как C++ — это объектно-ориентированный язык, мы будем постепенно переходить к объектам. Пока вам важно знать, что объект – это программное описание какой-либо сущности. Такой сущностью могут выступать люди, животные, предметы, явления, события или какой-то модуль кода.
Как это относится к тому, что мы уже изучили? Вернемся к нашему списку сотрудников из урока5: «Массивы». В этом списке было двадцать сотрудников. Каждый из них что-то делает уникальное. Допустим, один является бухгалтером, второй –менеджером, третий –рекламщиком и т.д. Иными словами, получается, что фирма – это не только материальные активы типа товаров, столов, компьютеров и т.д., но еще и исправно функционирующий механизм, построенный на взаимодействии сотрудников . К этой теме мы еще вернемся. А пока нам нужно знать, что эта в этой фирме есть отделы, решающие отдельные задачи (бухгалтерия, IT-отдел и т.п.). Все они входят в структуру фирмы и могут быть использованы в любой момент директором, хотя и могут быть в простое. Это устраивает всех. Трудно представить себе директора, который будет вести дела всех отделов одновременно.
Такая же аналогия и с программами. Если вы видите, что в вашей программе намечаются подпрограммы, непременно воспользуйтесь этим.
Например, вы пишите любимый всеми преподавателями вузов калькулятор. Ваша программа должна как минимум уметь складывать, вычитать, умножать и делить числа. Если вы упражнялись в программировании, то у вас может возникнуть такой вариант решения данной задачи:
#include <iostream>
#include <locale.h>
usingnamespacestd;
intmain()
{
setlocale(0,«»);
cout<<«Выберите действия:»
<<«1: Сложить два числа»
<<«2: Умножить два числа»
<<«3: Найти разность двух чисел»
<<«4: Разделить два числа»<<endl;
intn;
cin>>n;//вводим идентификатор действий
//для нашего удобства введем сюда иницализацию двух чисел
int val1,val2;
switch(n)
{
case1:cout<<«Введитечисло 1″<<endl;
cin>>val1;
cout<<«Введитечисло 2″<<endl;
cin>>val2;
cout<<«Суммуравна = «<<val1+val2<<endl;
break;
…//Остальные действия, я опущу их
default:
cout<<«Недопустимая операция»<<endl;
}
system(«pause»);
}
Как видите, мы используем селектор switch, в котором выбираем нужные нам действия. Я не стал писать все четыре варианта. Скажу лишь, что принцип там тот же самый.
Скажите, разве удобная эта программа? Я думаю, что нет. Вы запускаете ее, выполняете одно действие на выбор и программа выключается. Внимательный читатель скажет, что тут я не прав и можно это дело организовать в цикл do… while, выходом из которого будет вариант ввода , допустим 0. Это будет хорошим подходом, кстати, так делают при созданнии различных меню. Однако код у нас все же громоздкий. Кроме того, мы считаем здесь только целые числа, а ведь есть еще и вещественные. Что, нам делать еще дополнительные пункты меню, в которых мы будем считать числа типа double? Да пользователь такой программы пошлет незадачливого разработчика и будет прав.
Логично будет каждое действие описать виде подпрограмм, а в меню уже вызывать их. Кроме того, благодаря перегрузке функций, мы легко решим эту проблему с типами данных.
Подпрограммы в C++ представлены функциями, возвращающими значение и функциями, не возвращающие его (процедуры в других языках). Как понять возврат функцией значения? А здесь очень просто – если ваша подпрограмма подразумевает, что вместо каких-то действий оперировать одним значением, то ваша функция возвращает результат. Давайте приведу пример с функцией Сумму (пишу на псевдокоде, в C++ это работать не будет):
Сумма ()
{
Вернуть Число1+ Число2;
}
Теперь мы можем нашу Сумму распечатать на экране, или сделать с ней какие-то действия (у нас же калькулятор, вдруг мы захотим манипулировать с этим числом). Как видите, это очень удобно.
В противовес таким функциям, функции без возврата значений (повторю, почему-то в C нет термина процедура, хотя это одно и тоже) ничего не возвращают. Допустим, нам нужно поменять разрешение экрана ( мы это скоро будем делать в нормальных программах). Эта функция ничего не будет возвращать, да оно нам и не нужно (хотя, вы всегда сможете написать функцию, которая выводит на экран текущее разрешение монитора):
УстановитьРазрешение ()
{
Ширина=1024;
Высота=768;
}
Думаю, разницу вы поняли. Важно запомнить, что и та, и другая функция все равно выполнится, даже если вы не воспользуетесь возвращенным значением. Кроме того, второй тип функций может использовать также и при изменении нескольких типов значений, переданных по ссылке, но об этом поговорим через пару уроков.
Синтаксически функции выглядят так:
ТипВозвращаемогоЗначения ИмяФункции (тип_параметра имя параметра,…тип_параметра_n параметрN)
{
Действия;
Возврат вычисляемогоЗначения;
}
Тип значения функции может быть любым, известным системе, в том числе и созданным нами. Им же могут быть и указатель. Если функция ничего не возвращает, то ее тип будет указываться как void. Далее в скобках идет список параметров, которых может и не быть, тогда в скобках ничего не будет писаться. Параметры – это значения, которые мы даем подпрограмме для различных манипуляций с ними. Это могут быть значения из кода программы или же написаны с потолка. Давайте приведу пример:
stringWriteName(stringname)
{
returnname;
}
int main ()
{
cout<<WriteName(«Вячеслав»)<<endl;//напишет Вячеслав, вы же можете ввести свое имя
stringoprName=«Владимир»;// определим переменную в программе
cout<<WriteName(oprName)<<endl;//теперь мы ввели значение переменной, которую раньше уже опредлили в коде
return 0;
}
Как вы уже догадались, main – это тоже функция. Только она в отличие от остальных является точкой входа в программу. Это значит, что только она является главной по отношению к другим и может вызывать остальные функции. Хотя и сами функции могут вызывать другие функции.
Что означает «вызвать функцию»? Это подобно использованию в программе отдельного оператора. Мы можем вывести значение на экран, а можем вместо этого написать имя функции, которое будет делать тоже самое. Это будет равносильно тому, если бы мы написали вместо имени этой функции целый код из ее тела. Думаю, что с этим вы разобрались.
Важным моментом является понимание сути параметров. Запомните, если вы хотите передать функции какие-то значения, типа того как я давал своей функции значение имени, то параметры нужно использовать. В противном случае, они будут лишними.
Например, сумма будет выглядеть так:
intSumm(intval1,intval2)
{
returnval1+val2;
}
Я указал функции, что буду использовать два параметра. Как их назвать, дело ваше. Для программы это роли не играет. Кроме того, я могу указать функции больше параметров, чем это необходимо, вдруг я потом модифицирую функцию. Вот только не забудьте дать ей все эти параметры:
intSumm(intval1,intval2,intval3,stringvalue)
{
returnval1+val2;// использю только два параметра
}
//При вызове зополню параметры ()
cout<<Sum(23,12,54,«Это лишний параметр»);// сработает как нужно
Надеюсь, вы это поняли. Если это не так, то дальнейшее читать вам просто бессмысленно. Тогда вам нужно будет обратиться к учебникам, либо бросить эту затею.
А как же использовать функции, не возвращающие значения? Очень просто:
voidSetResolution(intp1,intp2)
{// функция устанавливает разрешение экрана, которое мы передадим в параметре
Screen.Width=p1;
Screen.Height=p2;
}
Этот пример, кстати, работает в C++ Builder, используя библиотеку VCL. В любом случае, правила те жи самые с параметром.
Есть еще одна маленькая особенность работы с функциями –мы можем выйти из нее, когда нам вздумается. Давайте разберем функцию деления двух чисел. Представим себе, что кто-то взял да и ввел 0 второй параметр, а на 0, как известно, делить нельзя. Тогда функция будет иметь вид:
doubleChastn(intv,intb)
{
if(b==0)
{
cout<<«На ноль делить нельзя!»<<endl;
return;// выходим из функции (хотя здесь правильно было бы вернуть -1, что обычно означает ошибку)
}
returnv/b;// если b не 0, то у нас все впорядке
}
Теперь при вызове функции и передаче ей вторым параметром 0, она будет выдавать сообщение и заканчивать свое выполнение.
Я обещал, что расскажу, что такое перегрузка функций. А это, уважаемые читатели, очень важный момент в C++.
Если имя параметра может быть любым, то его тип должен точно соответствовать указанной сигнатуре . Иными словами, если вы указали типом строку, то при передаче параметром числа компилятор вас выругает. Кроме того, мы вначале урока еще поняли, что значения функция может использовать разные. Мы можем объявить имя функции с указанием типа, который она будет использовать:
double SummD (double v1, double v2);
Но это же неудобно! Для этих целей была придумана перегрузка функций. Ее суть заключается в том, что можно одному имени функции создать несколько функций с тем же именем, но разными типами параметров и компилятор будет автоматически подставлять нужную функцию в прорамму. Вот пример:
doubleSum(inta,intb)
{
returna+b;
}
doubleSum(doublea,doubleb)
{
returna+b;
}
// в main
cout<<Sum(12.0,21.4)<<endl;// Поставит вторую функцию
Как видите, компилятор понял, что вы используете вещественные числа и сам подставил вторую функцию. И нам удобно, так как не нужно запоминать миллион функций с разными именами и программа выигрывает.
Параметрами могут выступать также и массивы, структуры, указатели и другие типы данных. Давайте продемонстрирую, как функция возвращает длину строки (мы же помним, что строка -это массив символов):
intLen(chars[])
{
inti=0;
while(s[i++])
returni—1;// пока индекс не выйдет за границы массива
}
// в main
charstr=» Кошкин дом»;
cout<<«Длина введенной строки равна «<<Len(str)<<endl;
А теперь самый главный момент. Все функции должны быть описаны строго во вне функции main!. Кроме того, существует несколько правил, по которым нужно объявлять функцию:
1. Объявление функции в стиле С. Это означает, что функцию нужно строго описать до ее вызова. Иными словами. Описание функции должно быть обязательно до функции main.
2. C++ относится несколько лояльнее. Здесь важно описать прототип функции до ее объявления, а описание можно втулить в конец программы, или в отдельный заголовочный файл, как это принято делать. В сигнатуре необходимо указать тип функции, ее имя и параметры, причем важную роль играют только типы параметров, а имена можно опустить
double Sum (int am int b);
double Sum (int, int) ; // ошибки здесь нет.
Существуют еще также встраиваемые функции (inline). В этом случае компилятор копирует текст кода этой функции в место, куда ее нужно подставить. Это намного быстрее, чем вызов обычной функции (не нужно копировать ее в память), но если этим злоупотреблять, то код неоправданно вырастет. Поэтому желательно использовать подставляемые функции в случае только острой необходимости.
Объявляется такая функция с ключевым словом inline.
inline int Sum (int, int); //прототип подставляемой функции.
В остальном ее использование такое же, как и у других. Поэкспериментируйте сами на досуге.
Ну и в заключении хотелось бы сказать, что раз функция возвращает какое-то значение, то логично ее подставить параметром в другую. Это широко используется в программировании. Кроме того, функции наряду с вызовом в своем теле другой функции, могут вызывать сами себя. Это называется рекурсией. Это полезный прием программирования, но о рекурсии я поговорю в уроках, посвященных алгоритмам.
Для закрепления материала предлагаю реализовать вам самостоятельно калькулятор, о котором я написал вначале урока, но с использованием функций и цикла с постусловием. Попытайтесь написать этот калькулятор самостоятельно. Привыкайте творчески мыслить.