7. Функции II

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


** Параметри-псевдоними

* Формалните параметри на функциите са два вида - параметри-променливи (value parameters) и параметри-псевдоними (reference parameters).

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

* Формален параметър-псевдоним не създава (не дефинира) нова променлива, а е друго име (псевдоним) на съществуваща променлива (фактически параметър) в извикващата функция.
* Те се озвначават с добавяне на знака & преди името на формалния параметър.

Пример:
Процедурата 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.
* Затова параметрите-псевдоними се наричат входно-изходни параметри - те "пренасят" данни от извикващата функция (main) към извиканата функция (raise_salary) и обратно.


** Константни псевдоними

* Използват се за да се избегне копирането - особено нежелателно, ако параметърът е обект, съдържащ много данни.
* Тъй като стойността на константния псевдоним не може да се променя, той не "пренася" данни от извиканата функция (print_employee) в извикващата функция (main). 

Пример: Функция ца отпечатване на данните на обект от тип Employee
void print_employee(const Employee & e) 
{ 
 cout << "Name:   " << e.get_name() 
      << "Salary: " << e.get_salary(); 
}
int main()

   Employee harry("Hacker, Harry", 45000.00);

   print_employee(harry);
   return 0;
}

Параметри-променливи Параметри-псевдоними Константни псевдоними
Входни параметри за функцията Входно-изходни параметри за функцията -
Предаване на параметри чрез копиране Предаване на параметри по адрес (не се копира)
Предаване на параметри по адрес (не се копира)
Стойността на параметъра може да се променя (фактическия параметър не се променя)
Стойността на параметъра може да се променя (фактическия параметър се променя)
Стойността на параметъра не може да се променя
Фактически параметър може да бъде константа, променлива или израз (rvalue)
Фактически параметър може да бъде само променлива (lvalue)
Фактически параметър може да бъде константа, променлива или израз (rvalue)

** Област на действие на променливите (scope), глобални променливи

* Ако едно и също име на променлива се използва в две функции,  то не съществува  връзка между двете променливи.
* Областта на действие на променлива, която е дефинирана в блока на функция, се простира единствено в рамките на тази функция. 

Пример:
Сложна лихва - променливи с едно и също име в две функциии.

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

* Променлива, дефинирана в блок на функция, се нарича локална променлива.
* Областта на действие на локална променлива е от мястото на дефинирането до края на блока, в който е дефинирана променливата (block scope).
* В тази област променливата е видима, т.е. може да бъде използвана. 

* Променлива, дефинирана извън блок на функция, се нарича глобална променлива.
* Областта на действие на глобална променлива е от мястото на дефинирането до края на файла, в който е дефинирана променливата (file scope).
* Досега са използвани глобалните променливи (обекти) 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

Проиграване:
int_name(20119)
n: 20119
c: 20119 | 119 | 19 | 0
r: twenty | twenty thousand | twenty thousand one hundred | twenty thousand one hundred nineteen

int_name(20)
n: 20
c: 20 | 0
r: twenty


** Предусловия и макрос assert.

* Какво трябва да направи една функция, ако е извикана с неподходящ вход?

* Има два варианта - да завърши нормално или да прекъсне работата на програмата.
-- Първият вариант е подходящ, когато функцията може да върне нещо разумно на извикващата функция - напр. digit_name от предишния пример връща празен низ.
-- Стандартните аритметични функции прекъсват работата на програмата със съобщение за грешка.

* Едно решение на проблема е да се използва макрос assert, който прекъсва работата на програмата при грешка.
- Когато се тества програмата, той дава мястото на "грешката" (некоректни входни данни на функция) в текста на програмата.
- Когато програмата е готова, може да се изключи проверката на  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;
}
nkirov@cpp % c++ futval0.cpp
nkirov@cpp % ./a.out
Please enter the interest rate in percent: 10
After 10 years, the balance is 2593.74
nkirov@cpp % ./a.out
Please enter the interest rate in percent: -10
Assertion failed: (p > 0), function future_value, file futval0.cpp, line 9.
zsh: abort      ./a.out

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