6. Функции I

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


** Функциите като черни кутии

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

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

Пример: Функцията за пресмятане на квадратен корен
sqrt(x)
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, int
Функция Тип на върнатата стойност
sqrt(x) double
c.substr(0, num) string
t.get_seconds() int
e.set_salary(s) no return value
Функция Изисквания за параметрите - тип и lvalue/rvalue
fabs(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;
}

nkirov@cpp % c++ futval1.cpp
nkirov@cpp % ./a.out
Please enter the interest rate in percent: 8
After 10 years, the balance is 2158.92

* Твърдо свързани стойности (начална сума 1000 и брой години 10) - да се избягват!
* Многократно използване на функцията (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
nkirov@cpp % c++ futval.cpp
nkirov@cpp % ./a.out
Please enter the interest rate in percent: 12
After 10 years, the balance is 3105.85


** Коментари и документиране
Пример:
/**
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;
}

nkirov@cpp % c++ approx.cpp
nkirov@cpp % ./a.out
Enter a number: 1
Enter another number: 1.0000000000000001
The numbers are approximately equal.
nkirov@cpp % ./a.out
Enter a number: 10
Enter another number: 10.001
The numbers are different.


** Параметри на функции

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

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)
{
    ...
}

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


** Странични ефекти и процедури
Пример: Процедура 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;
}

nkirov@cpp % c++ printime.cpp ccc_time.cpp
nkirov@cpp % ./a.out
Liftoff: 7:00:15
Now: 11:00:00

** Параметри-псевдоними 
Пример: Функция за увеличаване на заплата. 
Процедурата 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;
}