Функции. Дефиниране, основни елементи и обръщение към функция. Предаване на данни и връщане на резултат. Класове памет. Видимост и време на живот на променливите.
1. Функции
а) определение - функцията е блок от инструкции, който може да се извика от всяка една точка на програмата. Веднъж дефинирана, функцията може да се извиква многократно, като по този начин се опростява структурата на програмата;
б) дефиниране
тип име_на_функция (аргумент1, аргумент2, ... )
{
тяло на функцията;
}
Типът на функцията определя типа на връщания резултат.
Името на функцията се конструира по правилата за създаване на идентификаторите.
Аргументите са константи или променливи, които служат за входни параметри на функцията. Наричат се още формални параметри на функцията.
Тялото на функцията е блок от инструкции, които описват действието на функцията.
В края на тялото се поставя инструкция return, която връща стойността на функцията и в същото време продължава програмата от същото място, където е била извикана функцията.
При извикването на функцията параметрите, които се подават в скобите се наричат фактически.
в) извикване - функцията се извиква чрез нейното име и стойности на параметрите в скоби
Пример:
// използване на функция
#include "iostream.h"
int suma (int a, int b) // дефиниране на функция sum с параметри a и b
{
int s;
s = a + b;
return (s); // връщане на резултата
}
main( ) // входна точка на главната програма
{
int x, y;
cout << Въведете две числа \n"; // въвеждане на две числа x и y
cin >> x >> y;
cout << Сумата на двете числа е " << suma (x, y); // изчисляване на сумата чрез функция suma
}
В случая формални параметри са a и b -> описани в декларацията на функцията, а фактически параметри са x и y -> подавани при извикването на функцията.
Важно! Броя и типа на фактическите параметри трябва да съответства на броя и типа на формалните параметри.
г) използване на функции, които не връщат резултат – дефиниират се от тип void и не завършват с return
Пример:
// използване на функция, която извежда съобщение на екрана
#include "iostream.h"
void message (void)
{
cout << “Аз съм функция”;
}
main( )
{
message( );
}
В C++ при дефиниране на функции, които не връщат стойност, запазената дума void може да се изпусне. Ако липсват аргументи на функцията, тя се извиква с празни скоби.
д) предаване на параметри по стойност – формалните парамери на функцията получават копие на фактическите параметри
В примера по-горе се вижда, че при извикването на функцията suma параметрите, които подаваме са стойностите на променливите x и y.
Примери:
int chislo1=3, chislo2=5;
cout << suma ( chislo1, chislo2 );
или
cout << suma ( 3, 5 );
И в двата примера параметрите на функцията при извикването й са 3 и 5, като тези стойности се присвояват на формалните параметри a и b. При изпълнението на функцията suma стойностите на променливите chislo1 и chislo2 не се променят, защото функцията няма достъп до тези променливи.
е) предаване на параметри по адрес – формалните параметри получават адресите на фактическите параметри
Пример:
// начисляване на ДДС върху сума s
#include "iostream.h"
void DDS (float& s)
{
s=s*1.2; // Пресмятане на ДДС - в променливата s
}
main( )
{
float suma;
cout << “Въведете сумата в левове: ”
cin >> suma;
DDS (suma); // изчислява се ДДС – резултатът се получава в променливата suma
cout << “Сумата с ДДС е “ << suma;
}
В този случай при извикване на функцията DDS променливата s получава адреса на променливата suma, след което изчислява s=s*1.2, което води до променя на промяна и на suma. Т.е. всяка промяна на s вътре във функцията води до промяна на suma извън функцията.
Ако в горния пример се изпусне знака “&”, то при извикване на функцията DDS s ще получи копие на стойността на suma, а самата suma няма да се промени.
ж) функции с подразбиращи се параметри
Подразбиращи са тези параметри, които могат да се изпуснат при извикване на функция.
Пример:
#include "iostream.h"
int delenie (int a, int b=2) // параметър b=2 по подразбиране
{
int d;
d=a/b;
return (d);
}
main( )
{
cout << delenie ( 12 ); // изчислява 12 / 2, тъй като втория параметър е изпуснат
cout << delenie (20, 4 ); // изчислява 20 / 4
}
з) предефинирани функции
В C++ имаме право да дефинираме няколко функции с едно и също име, но при условие, че:
- функциите имат различни по тип или брой аргументи;
- функциите връщат различен по тип резултат.
Пример: // две функции, които извършват целочислено деление и деление с плаваща запетая
#include "iostream.h"
int delenie (int a, int b) // целочислено деление
{
int d;
d=a/b;
return (d);
}
float delenie (float a, float b) // деление с плаваща запетая
{
float d;
d=a/b;
return (d);
}
main( )
{
cout << delenie ( 15, 2 ); // резултат 7
cout << delenie ( 15.0, 2.0 ); // резултат 7.5
}
В този случай компилаторът определя коя от двете функции да използва по типа на аргументите – в първия случай аргументите са цели числа, а във втория – числата с плаваща запетая
и) inline функции
Директивата inline, поставена пред декларацията на функцията, указва на компилатора, че тялото на функцията трябва да се разположи на мястото, където тя се извиква в програмата. Така програмата се изпълнява по-бързо, защото не се налага да се прави преход към тялото на функцията и връщане след нейното изпълнение. Използва се при кратки функции, когато е необходимо повишаване на бързодействието на програмата.
inline тип име_на_функция (аргумент1, аргумент2, ... )
{
тяло на функцията;
}
2. Рекурсия - свойството на функцията да извиква сама себе си
Пример: Да се изчисли факториел на числото n по формулата:
n! = n * (n-1) * (n-2) * ... * 2 * 1
За n=5
n! = 5 * 4 * 3 * 2 * 1
#include "iostream.h"
long factorial (long a)
{
if ( a > 1 )
return ( a * factorial ( a-1 ) );
else
return (1);
}
main( )
{
long n;
cout << “Въведете n=”;
cin >> n;
cout << “n!=”<<factorial ( n );
}
Важно! Една рекурсивна функция трябва винаги да има условие за прекратяване на извикванията, защото в противен случай ще се получи безкраен цикъл.
3. Прототипи на функции
Досега функциите, които използвахме, се декларират винаги преди главната функция на програмата – функцията main ( ). Това се налага поради факта, че компилаторът анализира кода на програмата ред по ред и ако функцията е декларирана след реда, в който се извиква, ще възникне грешка от типа недефиниран идентификатор.
В случай, когато се налага потребителските функции да се декларират след функция main, се налага да се създадат прототипи на тези функции.
тип име_на_функция (аргумент1, аргумент2, ... );
Дефиницията на прототип не включва тялото на функцията и завършва с “ ; “. В скобите може да се включи само типа на аргументите, не е задължително да се посочват техните имена.
Пример:
#include “iostream.h”
void odd ( int a);
void even ( int a);
main ( )
{
int i;
do
{
cout << “Въведете число: “;
cin >> i;
odd (i);
}
while (i != 0 );
}
void odd ( int a);
{
if ( ( a%2 !=0 ) cout << “Числото е нечетно \n”;
else even (a);
}
void even ( int a);
{
if ( ( a%2 ==0 ) cout << “Числото е четно \n”;
else odd (a);
}
В примера се виждат прототипите на двете функции odd и even. В главната програма се извиква функция odd, която е дефинирана след функция main. Освен това всяка една от двете функции odd и even се извикват една-друга. Това е възможно само с предварителното дефиниране на техните прототипи.
Разрешено е да се дефинират прототипи и на всички функции, независимо дали те са дефинирани преди или след функция main. Това се препоръчва при създаване на програми с много функции, което спестява много време, когато трябва да се определи как трябва да се извика дадена функция.
4. Класове памет – определят областта на действие и периода на активност на променливите
Класовете памет биват 4 вида: extern, auto, static, register
Общ вид на задаването на класове памет:
име_на_клас_памет тип променлива
Примери:
auto float x;
static int a;
extern – клас памет, който обявява, че променливата е вече дефинирана в друг външен модул.
auto – клас памет, който се използва за дефиниране на вътрешни променливи във функции. Нямат предварително определени стойности. За тях се заделя място в стека. Това е подразбиращият се клас памет и може да се изпусне.
static – клас памет, който се използва за дефиниране на променливи с период на активност равен на времето на живот на цялата програма. За тях се заделя място в областта за данни на програмата. Променливите от този тип предварително се нулират от компилатора.
register – клас памет, който указва на променливите да се съхраняват в регистрите на процесора. Това се прави с цел увеличаване на бързодействието. Най-подходящи за такъв клас памет са променливите, участващи в цикъл.