6. Функции I

План:
Функциите като черни кутии
Писане на функции
Коментари и документиране
Връщане на стойност и оператор return
Параметри на функции
Деклариране на функции
Странични ефекти и процедури
Параметри-псевдоними


** Функциите като черни кутии
Функция е част от код, определена с име.
До сега сме използвали редица функции, които са предоставени:
-- от системните библиотеки на С++
    sqrt(x) - пресмята корен квадратен на числото с плаваща точка x
  getline(cin, s)
- чете един ред от потока cin
-- като член-функции от класа string на Стандартната библиотека с шаблони на С++
   
s.length() - връща дължината на низа s
--  от потребителските класове Time и Employee, дефинирани в учебника
  t.get_second() - връща секундите от обекта t на класа Time

Функциите могат да се разглеждат като черни кутии, като знаем само вход и изход, без да се интересуваме как работи функцията.

Пример:
int main()
{ cout << "Enter a positive number: ";
double x;
cin >> x;
x = sqrt(x); // sqrt is running
cout << "The result is " << x << "\n";
return 0;
}
- Изпълнението на main() временно се прекратява докато се изпълнява функцията sqrt.
- Функцията sqrt става активна и пресмята стойността за връщане, използвайки някой метод, който ще даде конкретен резултат.
- Върнатата стойност се прехвърля обратно на 
main, която продължава изпълнението като използва тази стойност (отпечатва я на екрана).
функция параметър
sqrt(x) променлива
sqrt(2.5) константа
sqrt(x + 1.02) израз
функция брой на параметрите
sqrt(x) 1
pow(x,y) 2
harry.get_salary() 0

член-функция неявен параметър тип на
неявен параметър

явен параметър
тип на
явен параметър
harry.get_salary() harry Employee няма
-
t.add_seconds(10) t Time 10
int
s.substr(0,num) s string 0, num
int, string
функция върната стойност
sqrt(x) double type
c.substr(0, num) string type
t.get_seconds() int type
getline(cin,s) no return value
функция изисквания за параметрите - тип и lvalue/rvalue
fabs(w) параметърът w трябва да е от тип  double - константа, променлива, израз (rvalue)
t.second_from(t1) параметърът t1 трябва да е обект от класа Time - константа, променлива
getline(cin,s) първият параметър трябва да бъде cin, вторият параметър трябва да е променлива (lvalue) от тип string

** Писане на функции
Пример: Да се пресметне стойността на влог с начална сума 1000 лв. след 10 години с годишната лихва да е p%. (Използва се формулата за пресмятане на сложна лихва.)
double future_value(double p)
{  double b = 1000 * pow(1 + p / 100, 10);
    return b;
}
Функцията се дефинира като се укаже:
- тип на резултата (на връщаната стойност),
- име на функцията,
- тип на параметъра,
- име на параметъра,
- тяло на функцията.

// futval1.cpp
#include
<iostream>
#include <cmath>
using namespace std;
 
// дефиниция на функция
double future_value(double p)
{  double b = 1000 * pow(1 + p / 100, 10);
    return b;
}
 
int main()
{  cout << "Please enter the interest rate in percent: ";
    double rate;
    cin >> rate;
 // извикване на функция
    double balance = future_value(rate);
    cout << "After 10 years, the balance is " 
         << balance << "\n";
 
    return 0;
}
Твърдо свързани стойности - да се избягват!
Многократно използване на функцията (reusable).
Друго решение на същата задача - сега функцията future_value има 3 параметъра: началната сума, годишната лихва и брой години на престоя на влога в банката.
double future_value(double initial_balance, double p, int n)
{ double b = initial_balance * pow(1 + p / 100, n);
return b;
}
double balance = future_value(1000, rate, 10);
futval.cpp

** Коментари и документиране
Всяка функция трябва да бъде документирана, като преди да се пише самата функция, се записват коментари по зададен шаблон.
В учебника е приет формата javadoc.

Пример:
/**
Computes the value of an investment with compound interest.
@param initial_balance - the initial value of the investment
@param p the interest rate per period in percent
@param n the number of periods the investment is held
@return the balance after n periods
*/
double future_value(double initial_balance, double p, int n)
{ double b = initial_balance * pow(1 + p / 100, n);
return b;
}
Може да се използва и показания формат за български език.
/* 
ЦЕЛ:       пресмята стойността на влог със сложна лихва 
ПОЛУЧАВА:  initial_balance - начална стойност на влога 
           p - годишен лихвен процент 
           nyear - броя на годините 
ЗАБЕЛЕЖКИ: лихвата се начислява всеки месец 
*/

** Връщане на стойност и оператор return
Когато се достигне до оператор return, от функцията се излиза веднага и управлението се връща на извикващата функция.
double future_value(double initial_balance, double p, int n)
{ if (n < 0) return 0;
if (p < 0) return 0;
double b = initial_balance * pow(1 + p / 100, n);
return b;
}
Всеки клон на функцията (вследствие на оператор if) трябва да връща някаква стойност.

Пример:
double future_value(double initial_balance, double p, int n)
{ if (p >= 0)
return initial_balance * pow(1 + p / 100, n);
/* Error */
}
Последният оператор на всяка функция би трябвало да бъде оператор return.

Пример: Функция за пресмятане на относителна близост на две числа.
bool approx_equal(double x, double y)
{ const
double EPSILON = 1E-14;
if (x == 0) return fabs(y) <= EPSILON;
if (y == 0) return fabs(x) <= EPSILON;
return fabs(x - y) / max(fabs(x), fabs(y)) <= EPSILON;
}
Функция, която връща булева стойност се нарича предикат.
 // approx.cpp
#include
<iostream>
#include <algorithm>
using namespace std;
/**
    Tests whether two floating-point numbers are 
    approximately equal.
    @param x a floating-point number
    @param y another floating-point number
    @return true if x and y are approximately equal
*/
bool approx_equal(double x, double y)
{  const double EPSILON = 1E-14;
    if (x == 0) return fabs(y) <= EPSILON;
    if (y == 0) return fabs(x) <= EPSILON;
    return fabs(x - y) / max(fabs(x), fabs(y)) <= EPSILON;
}
 
int main()
{  double x;
    cout << "Enter a number: ";
    cin >> x;
 
    double y;
    cout << "Enter another number: ";
    cin >> y;
 
    if (approx_equal(x, y))
       cout << "The numbers are approximately equal.\n";
    else
       cout << "The numbers are different.\n";
    return 0;
}

** Параметри на функции. 
* Формални параметри  - параметри-променливи, които се задават при дефиниране на функцията.

double future_value(double initial_balance, double p, int n)

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

b = future_value(total/2, rate, year2-year1);

Фактическите параметри съответстват по тип и брой на формалните параметри: променливите total и rate са от тип double, а year1 и year2 са от тип int . 

Формалните параметри са променливи, които може да се променят в тялото на функцията.

double future_value(double initial_balance, double p, int n)
{ p = 1 + p / 100;
double b = initial_balance * pow(p, n);
return b;
}

Често това се счита за лош стил на програмиране (bad programming style).


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

В езика C++ файлът с текста на програмата се подрежда най-често по следния начин: 
- декларация на функция,
- използване на функция,
- дефиниране на функция.
 
#include <iostream>
#include <cmath>
using namespace std;

/* декларация на функцията future_value */
double future_value(double initial_balance, double p, int nyear);

int main()
{ ...
    /* използване на функцията future_value */
    double bal = future_value(10000, rate, 10);
    ...
}

/* дефиниция на функцията future_value */
double future_value(double initial_balance, double p, int nyear)
{
    ...
}

Декларациите на стандартните за езика функции са в заглавните файлове.

** Странични ефекти и процедури. 
Външно забележимият ефект от работата на функция се нарича страничен ефект, напр. отпечатване на някакво съобщение, прекратяване работата на програмата и др.
Функциите по принцип не трябва да имат странични ефекти (пресмятат нещо и връщат стойност). 

Когато едно действие трябва да бъде извършено многократно (поне 2 пъти), има смисъл да бъде обособено като функция.
Функция, която не връща стойност се нарича процедура. 

Пример: Процедура print_time за отпечатване на обект от тип Time.
 // printime.cpp 
#include
<iostream>
#include <iomanip>
using namespace std;
 
#include "ccc_time.h"
/**
    Print a time in the format h:mm:ss
    @param t the time to print
*/
void print_time(Time t)
{  cout << t.get_hours() << ":" << setw(2) << setfill('0') 
         << t.get_minutes() << ":" << setw(2) 
         << t.get_seconds() << setfill(' ');
}
 
int main()
{  Time liftoff(7, 0, 15);
    Time now;
    cout << "Liftoff: ";
    print_time(liftoff);
    cout << "\n";
 
    cout << "Now: ";
    print_time(now);
    cout << "\n";
 
    return 0;
}


** Параметри-псевдоними. 
Досега използваните параметри на функции сe наричат параметри-променливи.
Параметрите-променливи са променливи, които се дефинират при извикване на функцията и са различни от променливите в извикващата функция.

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

Пример: Функция за увеличаване на заплата.
Процедурата raise_salary увеличава заплатата на служител e с by процента.

raise_salary(harry, 5);
void raise_salary(Employee& e, double by)
{
double new_salary = e.get_salary() * ( 1 + by / 100);
e.set_salary(new_salary);
}

 // raisesal.cpp
#include
<iostream> 
using namespace std;
 
#include
"ccc_empl.h"
 
/**
    Raise an employee salary 
    @param e employee receiving raise
    @param by the percentage of the raise
*/
void raise_salary(Employee& e, double 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;
}

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