7. Функции II

План:
Параметри-псевдоними
Константни псевдоними
Област на действие на променливите, глобални променливи
Постъпково прецизиране, създаване на кода, проиграване
Предусловия и макрос assert

**Параметри-псевдоними.
* Параметър-псевдоним не представлява нова променлива, а друго име (псевдоним) на съществуваща променлива в извикващата функция.
* Процедура raise_salary, която увеличава заплатата на служител с by процента

// reisesal.cpp 
#include <iostream>
#include "ccc_empl.h"

using namespace std; 

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. 
Променлива, дефинирана в блок на функция, се нарича локална променлива.
Областта на действие на локална променлива е от мястото на дефинирането до края на блока, в който е дефинирана променливата.
В тази област променливата е видима, т.е. може да бъде използвана. 

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

// 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

Глобалните променливи предават данни между функциите, което трябва да се избягва.
Използването им трябва да става само ако има основателна причина за това.
Нормалният обмен на данни между функциите става с помощта на параметрите на функциите. 
** Постъпково прецизиране, създаване на кода, проиграване.
Една от най-добрите стратегии за решаване на трудни задачи е декомпозицията (постъпково прецизиране) - разбиване на задачата на няколко по-прости задачи.
Получените задачи също се разбиват на по-прости и процесът продължава, докато се получат задачи, които могат да се решат директно.

Пример: Задача за записване на числата с думи. 

// 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 с директива на препроцесора. 
// futval0.cpp 
#include <iostream> 
#include <cmath> 
#include <cassert> 
using namespace std;

double future_value(double initial_value, double p, int n)
{  assert(p > 0);
   assert(n > 0);
   return initial_value * pow(1 + p / 100, n);
}

int main()
{  cout << "Please enter the interest rate in percent: ";
   double rate;
   cin >> rate;

   double balance = future_value(1000, rate, 10);
   cout << "After 10 years, the balance is "
        << balance << "\n";

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

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