Вообще-то, уже можно было бы приступить к написанию игры. Для использования функций вы знаете все что нужно. Но все же, пока вы серьезно настроены на учебу, я расскажу вам о функциях поподробнее. Я расскажу вам много интересного. Если же вам невтерпеж, можете сразу переходить к шестому уроку. Вот только в процессе вам все равно придется возвращатся, и перечитывать пропущенное.
Перегрузка функций
Идя по свежим следам, вновь вернемся к именам. Именам функций. Имя, которое используем мы и имя, которым функция представлена в программе, различны. В отличии от нас, программа также учитывает количество и тип аргументов а также тип возвращаемого значения. Все это вместе называют полным именем функции, ее сигнатурой. Пример:
Code
int func(); char func(); int func(int); int func(char); char func(int,int); char func(int,float);
Все это разные функции. Возможность создавать функции с одинаковыми именами называется перегрузкой функций. Может возникнуть вопрос, зачем же эта перегрузка нужна. Зачем сознательно вносить такую путаницу в программу. Оказывается для того, чтобы эти самые программы упростить. Понять это поможет следующий пример:
Code
int sum(int a, int b) { return a+b; } float sum(float a, float b) { return a+b; }
Здесь мы видим перегруженную функцию sum(). Альтернатива это создать функции с разными именами и все время мучится, вызывая правильную для конкретного типа данных. А так компилятор сам подберет нужную, сравнив передаваемые ей типы.
Необходимо отметить, что указанный выше пример работать в Microsoft Visual Studio 2008 не будет. Компиляция завершится следующей ошибкой:
error C2668: sum: неоднозначный вызов перегруженной функции
Поскольку компилятор сам неплохо умеет преобразовывать типы, новые стандарты требуют, чтобы во избежание таких вот ситуаций, программист явно указывал тип данных. Вот так:
Code
cout<<sum(10.5f,20.7f);
или так:
Code
cout<<sum((float)10.5,(float)20.7);
Значение аргументов по умолчанию.
И еще немного об именах. В С++ есть возможность присваивать используемым в функциях аргументам значение по умолчанию. Делается это очень просто. Достаточно выполнить присваивание в момент объявления функции:
Code
int func(int i=50);
Здесь аргументу, представленному переменной int i, по умолчанию присвоено 50. Оно будет использоваться, если при вызове не передавать функции никакого значения. Пример:
Code
#include <iostream> using namespace std;
void func(int i=50); // обьявление с заданием аргумента по умолчанию
void main() { func(); // первый вызов func(111); // второй вызов cin.get(); }
void func(int i) { cout<<i<<"\n"; }
Здесь при первом вызове функции не передается никакое значение, поэтому используется значение по умолчанию. При втором вызове используется 111.
Конечно же у функции может быть больше одного аргумента.
Code
void func(int arg1, int arg2, int arg3);
Присвоить значение по умолчанию аргументу arg2, можно только в том случае, если значение по умолчанию уже есть у arg3. Так делать нельзя:
Code
void func(int arg1, int arg2 = 125, int arg3);
Можно только так:
Code
void func(int arg1, int arg2 = 125, int arg3 = 0);
Если объявление функции не используется, то аргументы по умолчанию задаются в ее реализации:
void main() { func(); // первый вызов func(111); // второй вызов cin.get(); }
Встраиваемые функции
Использование функций имеет один маленький недостаток. На вызов функции тратится некоторое время. Происходит это потому, что под код функции резервируется отдельное место в памяти. По ходу выполнения программы, при вызове функции программе приходится переходить к этому месту, а затем возвращаться обратно. Естественно что этот переход не происходит мгновенно, и хорошо если она вызывается всего раз, а вот если 100, значит и переходов будет столько же, и потраченного времени в сто раз больше. Получается что получить большую скорость работы программы можно, вписывая код функции в место вызова. Это еще бы было нормально, если бы функция вызывалась всего пару раз, а вот если опять сотню. Это ж получится такой объем кода, что сам черт в нем не разберется. Чтобы обойти все эти проблемы, можно использовать оператор inline. Синтаксис следующий:
Этот оператор заставляет компилятор встраивать код функции в место ее вызова. Как говорится, и волки сыты и овцы целы, то есть и код небольшой и легко читаем, и программа работает быстро. Но все же есть одно но. Если такая функция вызывается 100 раз, то при компиляцию в программу будет вставлено 100 ее экземпляров. За увеличение скорости придется заплатить увеличением программного кода и занимаем местом в памяти. В результате никакого выигрыша может и не быть. Поэтому нужно руководствоваться следующим правилом: Если функция содержит всего две-три строки кода, то ее можно, и даже нужно, делать встраиваемой. Если же функция большая, да к тому же используется много раз, то делать ее встраиваемой нельзя.
Статические переменные.
Допустим, что нам зачем-то нужно посчитать, сколько раз конкретная функция вызывалась в программе. Первый пришедший на ум способ сделать это – создать отдельную глобальную переменную и в коде функции увеличивать ее на единицу. Вот так:
Code
#include <iostream> using namespace std;
int r=0; // глобальная переменная-счетчик
inline int sum(int a, int b) { r++; //использование глобальной переменной return a+b; }
void main() { for(int i =0;i<50;i++) { sum(10,20); } cout<<r; // результат подсчета cin.get(); }
Есть у подобного способа один недостаток. Допустим, вы скопировали вашу функцию в другую программу, или вообще дали своему другу. А в этой другой программе, ну или у друга, нет специально объявленной глобальной переменной. Программа работать не будет и хорошо, если вы вспомните почему. Естественно, способ решить подобную проблему есть. Это оператор - static. Объявив с его помощью переменную внутри функции
Code
int sum(int a, int b) { static int r=0; // статическая переменная-счетчик r++; return a+b; }
мы сделаем ее общей для всех экземпляров этой функции. Переменная создастся при первом вызове функции и будет существовать до конца работы программы. Указанная ситуация не единственная, где можно использовать статические переменные. Между прочим таким способом можно решить описанную выше проблему с областью видимости.
Рекурсивные функции
Может быть то, что я сейчас скажу, покажется вам бредом, но знайте, функция способна вызывать сама себя.
Code
int func() { func(); }
Называется эта возможность рекурсией. Рекурсия может быть прямая и косвенная. Прямая – когда функция вызывает сама себя, косвенная – когда функция вызывает другую функцию, которая в свою очередь вызывает первую. Кстати, это уже знакомая ситуация:
Code
void b();
void a() { b(); } ... void b() { a(); }
Применяется рекурсия в разных случаях, когда например нужно выполнить ее для какого то значения, а затем эта же функция применяется к полученному результату. Для рекурсивных функций является обязательным наличие условия, способного прекратить рекурсию. Небольшое откровение. Я не люблю рекурсию, особенно прямую. Нет такой ситуации, когда бы без нее нельзя было обойтись. К тому же множественный вызов функций, передача аргументов расходуют дополнительное время и память. Но все же есть одно преимущество - использование рекурсии позволяет сделать код более компактным. Специально приводить пример рекурсии я не буду.