8. Функции  ІІ
 
Да повтаряме е човешко,
рекурсията е божествена.
Л. Петер Дойч
 
Параметри-псевдоними.
* Параметър-псевдоним не представлява нова променлива, а друго име (псевдоним) на съществуваща променлива в извикващата функция.
* Процедура raise_salary, която увеличава заплатата на служител с by процента.
//reisesal.cpp
#include <iostream>
using namespace std;
#include "ccc_empl.cpp"

void raise_salary(Employee& e, double by)
/* ЦЕЛ:  увеличава заплатата на даден служител
   RECEIVES: e - служител, получаващ увеличение
             by - процент на увеличението
*/
{  double new_salary = e.get_salary() * (1 + by / 100);
   e.set_salary(new_salary);
}
int main()
{  Employee harry("Hacker, Harry", 45000.00);
   raise_salary(harry, 5);
   cout << "New salary: " << harry.get_salary() << "\n";
   return 0;
}

New salary: 47250

    Формалният параметър Employee& e на функцията raise_salary е параметър-псевдоним на променливата harry от главната функция main.Промяната на стойността на параметъра-псевдоним e във функцията raise_salary води до промяна и на стойността на фактическия параметър (променливата) harry от функция main. Затова параметрите-псевдоними се наричат още входно-изходни параметри - те "пренасят" информация от извикващата функция към извиканата функция и обратно.

Константни псевдоними.
    Използват се за да се избегне копирането - особено нежелателно, ако параметърът е обект, съдържащ много данни. Тъй като стойността на константния псевдоним не може да се променя, той не "пренася" информация от извиканата функция в извикващата функция.
void print_employee(Employee const & e)
{
 cout << "Name:   " << e.get_name()
      << "Salary: " << e.get_salary();
}
 

параметри-променливи параметри-псевдоними константни псевдоними
входни параметри за функцията входно-изходни параметри за функцията -
предаване на параметри чрез копиране предаване на параметри по адрес предаване на параметри по адрес
стойността на параметъра може да се променя стойността на параметъра може да се променя стойността на параметъра не може да се променя
фактически параметър може да бъде константа, променлива или израз фактически параметър може да бъде само променлива фактически параметър може да бъде константа, променлива или израз

Област на действие на променливите, глобални променливи.
    Ако едно и също име на променлива се използва в две функции,  то не съществува  връзка между двете променливи. Областта на действие на променлива, която е дефинирана в блока на функция, се простира единствено в рамките на тази функция.
//futval1.cpp
#include <iostream>
#include <cmath>
using namespace std;

double f_val(double i_bal, double p, int ny) /*дефиниции на параметрите-променливи i_bal, p и ny*/
{
  double r; /*дефиниция на променливата r в блока на функцията future_value*/
  r = i_bal*pow(1 + p/(12*100), 12*ny);
  return r;
} /*край на областта на действие на променливите i_bal, p, ny и r*/
int main()
{
 cout << "Interest rate in percent: ";
 double r; /*дефиниция на променливата r в блока на функцията main*/
 cin >> r;
 double bal = f_val(1000, r, 10); /*дефиниция на променливата bal*/
 cout << "After 10 years, the balance is " << bal << "\n";
 return 0;
} /* край на областта на действие на променливите r и bal*/

    В примера няма връзка между двете променливи с име r в двете функции future_value и main.
    Променлива, дефинирана в блок на функция, се нарича локална променлива. Областта на действие на локална променлива е от мястото на дефинирането до края на блока, в който е дефинирана променливата. В тази област променливата е видима, т.е. може да бъде използвана.
// global.cpp
#include <iostream>
using namespace std;
#include "ccc_empl.cpp"

double annual_raise; /* дефиниране на глобална променлива */
void raise_salary(Employee& e)
{ double new_salary = e.get_salary()*(1 + annual_raise/100);
  e.set_salary(new_salary); /* използване на глобалната променлива */
}
int main()
{  Employee boss("Reindeer, Rudolf", 48000.00);
   Employee harry("Hacker, Harry", 35000.00);
   annual_raise = 5; /* използване на глобалната променлива */
   raise_salary(boss);
   raise_salary(harry);
   cout << boss.get_name() << " " << boss.get_salary() << "\n";
   cout << harry.get_name()<< " " << harry.get_salary()<< "\n";
   return 0;
}

Reindeer, Rudolf 50400
Hacker, Harry 36750

    Променлива, дефинирана извън блок на функция, се нарича глобална променлива. Областта на действие на глобална променлива е от мястото на дефинирането до края на файла, в който е дефинирана променливата. Досега са използвани глобалните променливи (обекти) cin и cout, които са дефинирани в заглавния файл iostream.h. Глобалните променливи предават данни между функциите, което трябва да се избягва. Използването им трябва да става само ако има основателна причина за това. Нормалният обмен на данни между функциите става с помощта на параметрите на функциите.



Постъпково прецизиране, създаване на кода, проиграване.
    Една от най-добрите стратегии за решаване на трудни задачи е декомпозицията (постъпково прецизиране) - разбиване на задачата на няколко по-прости задачи. Получените задачи също се разбиват на по-прости и процесът продължава, докато се получат задачи, които могат да се решат директно.
    Задача за записване на числата с думи.
// intname.cpp
#include <iostream>
#include <string>
using namespace std;

string digit_name(int n)
/* ЦЕЛ:  превръща цифра в английското й име
   ПОЛУЧАВА: n - цяло число между 1 и 9
   ВРЪЩА:  името на n ("one" . . . "nine")
*/
{  if (n == 1) return "one";
   else if (n == 2) return "two";
   else if (n == 3) return "three";
   else if (n == 4) return "four";
   else if (n == 5) return "five";
   else if (n == 6) return "six";
   else if (n == 7) return "seven";
   else if (n == 8) return "eight";
   else if (n == 9) return "nine";
   return "";
}
string teen_name(int n)
/* ЦЕЛ:  превръща цяло число между 10 и 19 в английското му име
   ПОЛУЧАВА: n - цяло число между 10 и 19
   ВРЪЩА:  името на n ("ten" . . . "nineteen")
*/
{  if (n == 10) return "ten";
   else if (n == 11) return "eleven";
   else if (n == 12) return "twelve";
   else if (n == 13) return "thirteen";
   else if (n == 14) return "fourteen";
   else if (n == 15) return "fifteen";
   else if (n == 16) return "sixteen";
   else if (n == 17) return "seventeen";
   else if (n == 18) return "eighteen";
   else if (n == 19) return "nineteen";
   return "";
}
string tens_name(int n)
/* ЦЕЛ:  дава английското име на десетиците между 20 и 90
   ПОЛУЧАВА: n - цяло число между 2 и 9
   ВРЪЩА:  името на 10*n ("twenty" . . . "ninety")
*/
{  if (n == 2) return "twenty";
   else if (n == 3) return "thirty";
   else if (n == 4) return "forty";
   else if (n == 5) return "fifty";
   else if (n == 6) return "sixty";
   else if (n == 7) return "seventy";
   else if (n == 8) return "eighty";
   else if (n == 9) return "ninety";
   return "";
}
string int_name(int n)
/* ЦЕЛ:  превръща цяло число в английското му име
   ПОЛУЧАВА: n - цяло положително число < 1000000
   ВРЪЩА:  името на n (т.е. "two hundred seventy four")
*/
{  int c = n; /* оставащата за превръщане част */
   string r;  /* стойността на функцията */
   if (c >= 1000)
   {  r = int_name(c / 1000) + " thousand";
      c = c % 1000;
   }
   if (c >= 100)
   {  r = r + " " + digit_name(c / 100) + " hundred";
      c = c % 100;
   }
   if (c >= 20)
   {  r = r + " " + tens_name(c / 10);
      c = c % 10;
   }
   else if (c >= 10)
   {  r = r + " " + teen_name(c);
      c = 0;
   }
   if (c > 0)
      r = r + " " + digit_name(c);
   return r;
}
int main()
{  int n;
   cout << "Please enter a positive integer: ";
   cin >> n;
   cout << int_name(n);
   return 0;
}

Please enter a positive integer: 20119
 twenty thousand one hundred nineteen

Предусловия и макрос assert.
    Какво трябва да направи една функция, ако е извикана с неподходящ вход? Има два варианта - да завърши нормално или да прекъсне работата на програмата. Първият вариант е подходящ, когато функцията може да върне нещо разумно на извикващата функция - напр. digit_name от предишния пример връща празен низ. Стандартните аритметични функции прекъсват работата на програмата със съобщение за грешка.
    Едно решение на проблема е да се използва макрос assert. Когато се тества програмата, той дава мястото на "грешката" (некоректни входни данни на функция) в текста на програмата. Когато програмата е готова, може да се изключи проверката на  assert с директива на препроцесора.
//fincalc.cpp
#include <iostream>
#include <cmath>
#include <assert.h>
using namespace std;

double future_value(double initial_balance, double p, int nyear)
{ assert(nyear>=0 && p>=0);
  return initial_balance*pow(1 + p/(12*100), 12*nyear);
}
int main()
{ cout << future_value(1000,6,-5);
  return 0;
}

fincalc.cpp:8: failed assertion `nyear>=0 && p>=0'
abnormal program termination

    Ако условието в assert е изпълнено, нищо не се случва. Ако обаче изразът има стойност false, програмата прекратява работата си със съобщение от макрос assert.



Рекурсия
* Функцията  n!  (n факториел),  n! = 1.2.3.4...n. Рекурентна дефиниция на същата функция е:
0! = 1,  n! = n(n-1)!
Ако трябва да се пресметне например 4! по тази дефиниция, трябва да се направи следното: Записва се
4! = 4.3!
3! = 3.2!
2! = 2.1!
1! = 1.0!
0! = 1
и връщайки се обратно по записа, се пресмята
1! = 1.0! = 1.1 = 1
2! = 2.1! = 2.1 = 2
3! = 3.2! = 3.2 = 6
4! = 4.3! = 4.6 = 24.
 
n n!
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
 
    Програма за пресмятане на n!
// fac.cpp
#include <iostream>
using namespace std;

long factorial(int n)
{  if (n == 0) return 1;
   else
   { long result = n * factorial(n - 1);
     return result;
   }
}
int main()
{  cout << "Please enter a number: ";
   int n;
   cin >> n;
   cout << n << "! = " << factorial(n) << "\n";
   return 0;
}

Please enter a number: 12
12! = 479001600

    Процесът, при който една функция вика себе си, се нарича рекурсия, а самата функция се нарича рекурсивна функция. Тя трябва да изпълнява две условия:
    1. Всяко рекурсивно извикване трябва да опростява пресмятанията по някакъв начин. В случая функцията се извиква с по-малка стойност на параметъра.
    2. Трябва да има специална обработка на най-простите случаи. В нашия пример това е случаят когато входният параметър n е нула.