// елем.в i-ти ред
}
getch();
}
5.10. ВГРАДЕНИ ФУНКЦИИ
Едно от предимствата на функциите се състои в това, че техният код се съхранява само на едно място в паметта, независимо от броя на изпълненията им в дадена програма. По този начин силно се намалява разходът на памет. Но икономията на памет е за сметка на бързодействието, тъй като всяко обръщение към дадена функция е свързано с извършването на някои служебни операции (запомняне на възвратния адрес, предаване на параметрите и преход към стартовия адрес на дадената функция и др.), които отнемат известно време. В конвенционални програми подобно забавяне с без значение, но има програми, в които с важно да се постигне максимално бързодействие. Примери за такива програми са програмите за управление на различни устройства и технологични процеси. Програми от този вид работят в режим на реално време, което означава, че работата им трябва да бъде съобразена с различни временни ограничения и съотношения, зависещи от управляваните обекти. Подобни програми понякога могат да бъдат особено чувствителни по отношение на бързодействието.
За да се повиши бързодействието, С++ поддържа т.нар. вградени функции (inline functions). Кодът на тези функции се копира на всяко място в паметта, където има обръщение към тях. По този начин броят на служебните операции, които трябва да се изпълняват при извикване на вградените функции намалява, което води до увеличаване на бързодействието.
Вградените функции се дефинират и използуват както всички останали функции с тази разлика, че дефинициите им започват с ключовата дума inline. Ето един пример:
inline void bell(void)
{
cout << '\a';
}
Изпълнението на тази функция предизвиква издаване на звуков сигнал от зумера на компютъра.
Прспоръчително е вградените функции да бъдат "къси". В противен случай разходът на памет може да се увеличи твърде много, тъй като техният код се копира многократно.
5.11. ВИДОВЕ ПРОМЕНЛИВИ. ОБЛАСТ НА ДЕЙСТВИЕ НА ПРОМЕНЛИВИТЕ И ФУНКЦИИТЕ.
Всеки идентификатор (име на променлива, функция, клас, обект и пр.) има определена област на действие. Областта на действие на един идентификатор обхваща частите на една програма (функции, обекти, файлове, части от файлове), в които този идентификатор може да се използува. Областта на действие се нарича още "област на видимост", или просто "видимост". В тази глава се разглеждат правилата, определящи видимостта на променливите и функциите. С малки изключения тези правила са валидни и по отношение на класовете и обектите, на които с посветена следващата глава.
5.11.1. Автоматични променливи
Автоматични променливи са параметрите на функциите и променливите, дефинирани в телата на функциите. Автоматичните променливи имат две основни характеристики:
- Достъпни (видими) са само за функцията, в коятоса дефинирани, т.е. автоматичните променливи са локални по отношение на функциите.
- Създават се при всяко извикване на функцията, в която са дефинирани и престават да съществуват след нейното завършване.
Поради локалния характер на автоматичните променливи, в различните функции могат да се използуват автоматични променливи с еднакви имена. Автоматичните променливи могат да се инициализират при тяхното дефиниране, например:
f ( int х, int y )
{
//дефиниции на автоматични променливи
float р = 3.45; //Инициализация на променливата р
int m = 2, n = 3; //Инициализация на променливите m и n
char *s = "string1"; //Инициализация на указател към char
. . . . . . . . . . . . .
. . . . . . . . . . . . .
}
Автоматичните променливи р, m, n и s ще се инициализират наново всеки път, когато функцията бъде извикана, тъй като след завършване на изпълнението на функцията, автоматичните променливи престават да съществуват и техните стойности се губят.
Локалността на автоматичните променливи и временното им съществуване се дължат на механизма на заделяне на памет за тях. Паметта, необходима за автоматичните променливи на дадена функция, се заделя в стека всеки път, когато функцията бъде извикана. Практически това се осъществява чрез преместване на стековия указател на необходимия брой позиции. Докато трае изпълнението на функцията, заделената в стека памет е достъпна за нея. След завършване на изпълнението на функцията, старото положение на стековия указател се възстановява, което е еквивалентно на освобождаване на паметта за автоматичните променливи. Нещо повече, освободената в стека памет се използува за автоматичните променливи на следващата функция, която бъде извикана. Следователно използуването на автоматични променливи води до икономия на памет. Това обаче не е единствената причина, поради която езикът C++, а и други съвременни езици поддържат автоматични променливи. Освен икономия на памет, чрез автоматичните променливи се реализира механизмът на предаване на парамстри на функциите по стойност (параметрите са автоматични променливи). И накрая, без наличие на автоматични променливи не е възможно реализирането на рекурсивни функции.
5.11.2. Външни променливи
Външни променливи са тези, които са дефинирани извън тялото на която и да е от функциите, съставящи дадена програма. (В общия случай външни могат да бъдат не само променливите, но и масивите, структурите и обектите). Външните променливи се различават от автоматичните по следното:
- Външните променливи се създават еднократно при стартиране на програмите и съществуват през цялото време на тяхното изпълнение. Външните променливи не се разполагат в стека, а в друга област от паметта, наречена статична памет.
- Външните променливи са достъпни за множество функции. Възможно е дадена външна променлива да бъде достъпна за всички функции на дадена програма. В този смисъл външните променливи са глобални. Те са аналози на COMMON областите във FORTRAN и на глобалните променливи в PASCAL.
Външните променливи се използуват основно в два случая. Първият от тях се отнася до функции с голям брой параметри. Всяко извикване на такива функции изисква предаване на дълъг списък с фактически параметри, от което програмите стават по-трудно разбираеми. За да се намали броят на параметрите, част от тях могат да бъдат заменени с външни променливи, до които съответните функции имат достъп. Тази стъпка обаче може да се предприеме само в случаите, в които е без значение дали променливите са автоматични или не. Например, параметрите на рекурсивните функции не могат да бъдат заменени с външни променливи, тъй като рекурсията изисква използване на автоматични променливи, както, беше изяснено в предната точка.
Вторият основен случай, в който се използуват външни променливи, се отнася до функции, които работятс обши данни и нямат обръщения една към друга. Тогава, за да се осигури достъп на отделните функции до общите данни, тези данни трябва да се съхраняват чрез външни променливи.
Областта на действие на външните променливи може да обхваща част от даден файл, цял файл или няколко файла. Основното правило, определящо областта на действие на външните променливи, е следното: Областта на действие на една външна променлива се простира от мястото на нейната дефиниция до края на файла. Това означава, че достъп до дадена външна променлива ще имат само функциите, чиито дефиниции са разположени след дефиницията на външната променлина. Дефинирането и използуването на външни променливи е илюстрирано и програма 5.13.
Програма 5.13. Дефиниране и видимост на външни променливи
#include
int х = 1; //Дефиниция на външна променлива х
//Дефиниция на функция f1
f1()
{
x++;
cout << "f1: " << "x=" << х << '\n' ;
}
int у = 0; //Дефиниция на външна променлива у
//Дефиниция на функция f2
f2()
{
y += х;
cout << "f2: " <<"х=" << х << " у=" << у << '\n';
}
int z = 2; //Дефиниция на външна променлива z
//Дефиниция на фунрция f3
f3()
{
z = х + у;
cout << "f3: " <<"х=" << х << " у=" << у << " z=" << z << '\n';
}
//Дефиниция на функция main
main()
{
f1();
f2();
f3();
}
В тази програма са дефинирани три външни променливи - х, у и z. Дефиницията на променливата х се намира преди функциите main, f1, f2 и f3, поради което и всички тези функции имат достъп до нея. Променливата у е достъпна за функциите f2, f3 и main, но не и за функцията f1, тъй като дефиницията на функцията f1 е разположена преди дефиницията на променливата у. И накрая, променливата z е достъпна само за функциите f3 и main. Резултатът от изпълнението на програма 5.13 ще бъде следния:
f1: х = 2
f2: х = 2 у = 2
f3: x = 2 y =2 z = 4
5.11.3. Автоматични и външни променливи с еднакви имена. Оператор за принадлежност ::
Езиците С++ и С допускат наличието на автоматични и външни променливи с еднакви имена. Ако в една функция са дефинирани автоматични променливи, чиито имена съвпадат с имената навъншни променливи, функцията няма да има пряк достъп до външните променливи, тъй като имената на автоматичните променливи "засенчват" имената на външните променливи. В езика С++ (но не и в езика С) този проблем се решава чрез използуване на оператора ::, който се нарича оператор за принадлежност (оригиналният английски термин е scope resolution operator). Поставянето на оператора :: пред името на дадена променлива означава външна променлива с указаното име. Като пример за използуване на оператора :: за разграничаване на външни и автоматични променливи с еднакви имена с показана програма 5.14.
Програма 5.14. Автоматични и външни променливи с еднакви имена. Използуване на оператора ::
#include
int х; //Външна променлива
int у = 4; //Външна променлива
//Дефиниция на функция main
main()
{
int x = 2; //Автоматична променлива
int у = 3; //Автоматична променлива
х += у;
cout << "х=" << х << '\n';
у = ::у + 5;
cout << "у=" << у << '\n';
}
Във функцията main са дефинирани автоматичните променливи х и у, чиито имена съвпадат с имената на външните променливи х и у. Навсякъде в тялото на функцията main имената х и у се свързват с автоматичните променливи. Затова стойността на х след изпълнение на израза х += у; ще бъде 5 (х има стойност 3, а у има стойност 2). Предпоследният ред на функцията main съдържа израза у = ::у + 5;. Операторът ::, поставен пред името у означава, че у e външна променлива. Следователно горният израз се разбира така: На автоматичната променлива у се присвоява сумата от стойностите на външната променлива у и числото 5, т.е. числото 9.
Операторът за принадлежност :: има и други приложения, които са свързани с класовете и обектите и са разгледани в следващата глава.
5.11.4. Разделна компилация. Декларации на променливи и функции
Една програма на С++ може да бъде разположена в няколко файла, които се компилират независимо един от друг, т.е. осъществява се разделна компилация. В резултат на компилацията се получават няколко обектни модула (файлове с разширение .obj). Изпълнимият код напрограмата (файл с разширение .ехе) се получава след свързване на обектните модули. Преимуществото на разделната компилация се състои в това, че при промени в някои от файловете не e необходимо прекомпилиране на останалите. В резултат на това се съкращава времето за компилация, а от там и времето за разработка на програмите.
Когато една програма се състои от множество файлове, възникват ситуации, при които външни променливи, дефинирани в един файл, трябва да бъдат достъпни за функции, дефинирани в други файлове. Такъв е случаят, когато няколко функции са дефинирани в различни файлове, но имат общи данни. Тези общи данни трябва да бъдат външни променливи, до които всички функции да имат достъп. За да се осигури такъв режим на достъп, е необходимо външните променливи да бъдат дефинирани еднократно в някой от файловете, а в останалите файлове да бъдат декларирани като extern (extern е ключова дума). Ще поясним това с един пример: Нека в един файл е дефинирана външната променлива float х. За да бъде тя достъпна и за функции, намиращи се в други файлове, във всеки от тези други файлове трябва да бъде включена декларацията extern float х;. Ключовата дума extern е указание за компилатора, че променливата float х e дефинирана в друг файл. Затова, компилаторът не заделя памет за променливата х, а свързващият редактор търси нейния адрес в друг обектен модул (файл с разширение obj, който се създава от компилатора). Програма 5.15 e конкретен пример за използуване на една и съща външна променлива в три различни файла с имена pr0515.cpp, pr0515a.cpp и pr0515.cpp, които се компилират независимо един от друг.
Програма 5.15. Програма с разделна компилация, вътрешни статични променливи и външни променливи. Отделните елементи на програмата се намират във файловете pr0515.cpp, pr0515а.cpp и pr0515b.cpp
//Съдържание на файла pr0515.cpp:
#include
#include "pr0515a.cpp"
#include "pr0515b.cpp"
# include
extern int x; //x е външна променлива, дефинирана в друг фаил
void f1(); // Декларация на функцията f1
void f2(); // Декларация на функцията f2
main()
{
f1();
f2();
f1();
f2();
f1();
x+=2;
cout<<"x="<< x <<'\n';
getch();
}
//Съдържание на файла pr0515а.cpp:
#include
int x=2; // Дефиниране на външна променлива
void f1()
{
static int y=0; // y - статична вътрешна променлива
int z=0; // z - автоматична променлива
x += 2; y+=2; z+=2;
cout <<"x=" << x <<' '<<"y=" << y <<' '<<"z=" << z<< '\n';
}
//Съдържание на файла pr0515b.cpp:
#include
extern int x; // x е външна променлива, дефинирана в друг фаил
void f2()
{
x += 5;
cout <<"x=" << x << '\n';
}
Резултатът от изпълнението на програмата е:
х = 4
х = 9
х = 9
И трите функции f1, f2 и main използуват външната променлива х, т.е. х играе ролята на общи данни за тези функции. Във файловете pr0515b.cpp и pr0515.cpp e включена декларацията extern int х;, тъй като променливата х e дефинирана във файла pr0515a.cpp, а функциите f2 и main трябва да имат достъп до нея.
Обърнете внимание на декларациите void f1(); и void f2(); във файла pr0515.cpp. В началото на главата беше посочено, че дадена функция трябва да бъде дефинирана или поне декларирана преди мястото на нейното извикване. Тъй като функцията main извиква функциите f1 и f2, които не са дефинирани във файла pr0515.cpp, където има обръщение към тях, то във файла pr0515.cpp са необходими техните декларации. Декларациите на функциите имат същото значение, както и декларацията extern float х;, но когато се декларират функции ключовата дума extern не е необходима.
Декларациите на променливи и функции обикновено се обособяват в отделни файлове, които като правило имат разширение h (header files). Чрез директивата #include на прспроцссора тези файлове се включват във всички други файлове, в които са необходими декларациите. Тази техника има две предимства:
- декларациите не се размножават явно във всеки файл, което намалява възможностите за грешки при въвеждането на текста на програмите;
- при необходимост от добавяне на нови декларации или промяна на старите, не се налага тона да се извършва в множество файлове, а само в един файл - този, който съдържа декларациите.
5.11.5. Статични променливи.
Статичните променливи се дефинират чрез ключовата дума static. Те са два вида: вътрешни и външни. Вътрешните статични променливи са тези, които са дефинирани в тялото на някоя функция, подобно на автоматичните променливи. Външните статични променливи се дефинират извън тялото на която и да е функция, подобно на обикновените външни променливи.
5.11.5.1. Статични вътрешни променливи
Статичните вътрешни променливи имат същата област на действие както автоматичните променливи, т.е. те са достъпни само в рамките на функциите, в които са дефинирани. Но за разлика от автоматичните променливи, статичните вътрешни променливи се създават само веднъж при стартиране на програмите и съществуват през цялото време на тяхното изпълнение. По този белег статичните вътрешни променливи приличат на външните променливи. Накратко, статичните вътрешни променливи са локална постоянна памет на функциите, в които са дефинирани.
Основно приложение на статичните вътрешни променливи е съхранението на данни (стойности), които се получават в резултат от изпълнението на дадена функция и се използуват от същата функция при следващо нейно изпълнение.
Като пример за функция от този вид по-надолу е показана дефиницията на функцията filter, която реализира цифров филтър от първи ред. Цифровите филтри от първи ред се описват със следните две уравнения:
y(t) = а * x(t) + b * y(t-l)
y(t0) = m
където y(t) е стойността на филтрирания сигнал в момента t, x(t) е стойността на зашумения сигнал в момента t, y(t-l) е стойността на филтрирания сигнал в предидущия момент, а константата m е стойността на у в началния момент t0.
Реализацията на тези уравнения може да се осъществи чрез една функция, която трябва да се активира във всеки момент t (в реалния случай през определен интервал от време) и като резултат да връща стойността на y(t). При такава постановка на задачата, стойността на y(t-l), която е необходима за изчисляване на стойността на y(t), ще бъде резултатът от предидущото изпълнение на функцията. Поради тази причина е удобно резултатът от изпълнението на функцията да се съхранява в една статична вътрешна променлива. Ето как изглежда дефиницията на функцията filter, реализираща цифров филтър от първи ред:
float filter ( float a, float Ь, float х, float m, int flag )
{
static float у; //Статична вътрешна променлива
if ( flag == 0 ) //flag е 0 при първото извикване на функцията
return у = m; //y(t0)
return у=а*х+b*у; // y(t)'
}
Параметрите а и b представляват коефициентите на филтъра, х е стойността на входния (зашумения) сигнал в момента t, a m е стойността на изхода на филтъра в началния момент (при първото извикване на функцията). Параметърът flag служи за разграничаване на първото извикване на функцията от следващите нейни извиквания. При първото извикване параметърът flag трябва да има стойност 0, а при следващите - ненулева стойност.
5.11.5.2. Статични външни променливи.
Статичните външни променливи се дефинират извън тялото на която и да е функция, като дефиницията им започва с ключовата дума static. Областта им на действие съвпада с областта на действие на обикновените външни променливи, т.е. от мястото на дефиницията им до края на файла. Но за разлика от обикновените външни променливи, статичните вьншни променливи не могат да бъдат декларирани като extern в други файлове и следователно са недостъпни за функции, дефинирани в други файлове.
5.11.6. Статични функции
Функциите също могат да бъдат статични, което се указва чрез ключовата дума static, поставена пред дефинициите им. Областта на действие на статичните функции съвпада с тази на статичните външни променливи, т.е. статичните функции са видими само във файла, в който са дефинирани. Тъй като видимостта на статичните функции и на статичните външни променливи е ограничена в рамките на един единствен файл, то в други файлове могат да бъдат дефинирани външни променливи и функции със същите имена и прототипи.
Чрез статичните външни променливи и функции може да се реализира идеята за "скриване на данните" (data hiding), която е в основата на модулното програмиране. Например, ако в един файл бъдат обособени няколко статични външни променливи и статични функции, то достъпът до тези променливи ще се осъществява само и единствено чрез споменатите функции. По този начин могат да се оформят различни модули със строго регламентиран достъп до данните. В програма 5.16 е показан един такъв модул, съдържащ данни и функции, чрез които се реализпра опашка.
Програма 5.16. Реализация на опашка. Статични променливи и функции
//Съдържание на файл pr0516.cpp
#include
#include
#include "pr0516a.cpp"
#include //Заради toupper()
void dispatch ( char ); //Декларация на функция dispatch, koяmo е дефинирана
//във файла pr0516а.cpp
//Дефиниция на функцията main
main()
{
char com;
do {
cout<< "I - Въвеждане O - Извеждане C - Нулиране Q - Изход:";
cin>> com;
com=toupper(com); //Преобразуване в главни бykвu
cout<<'\n';
dispatch(com);
} while (com != 'Q');
getch();
}
//Съдържание на файл pr0516а.cpp - модул, сьдържащ данни и функции, реализиращи //onaшka
#include
#define BOOLEAN int
#define TRUE 1
#define FALSE 0
const MAX=5; //Максимална дължина на опашката
//Дефиниции на статични данни
static int first = 0; //Първи елемент в опашката
static int last = 0; //Последен елемент в опашката
static int count = 0; //Брой елементи в опашката
static int buf[MAX]; //Буфер на опашката
//Статична функция put_last - вмъква елемент в опашката
static BOOLEAN put_last(int &val)
{
if ( count < MAX ){ //Ako onaшкama не е пълна
count++;
buf[last] = val;
if ( ++last > MAX-1 ) last = 0;
return TRUE;
}
else return FALSE; //Пълна onaшкa
}
//Статична функция get_first - извлича елемент от опашката
static BOOLEAN get_first( int &val )
{
if ( count == 0 )
return FALSE; //Празна опашка
else {
count--;
val = buf[first];
if ( ++first > MAX-1 ) first = 0;
return TRUE;
}
}
//Статична функция clear - изчистване на опашката
static void clear()
{
count = first = last = 0;
}
//Дефиниция на функция dispatch - интерфейс за работа с опашката
void dispatch(char com)
{
int value;
switch ( com ){ //com съдържа подадената кoмaндa
case 'I': //Вмъкване на елемент
cout value;
if( put_last(value))
cout << "OK\n\n";
else cout << "\nПълна onaшкa!\n\n";
break;
case 'O': //Извличане на елемент
if (get_first(value))
cout<<"\nРезултат :"<
else cout << "Празна onaшкa\n\n";
break;
case 'C' : //Изчистване на опашката
clear();
break;
case 'Q': break;
default: cout<<"\nНеправилна кoмaндa\n\n";
}
}
Програма 5.16 се състои от два файла – pr0516.cpp и pr0516a.cpp. Файлът pr0516a.cpp представлява модул, чрез който с реализирана опашка. Оптиката е структура от данни, подчиняваща се на дисциплината "първи влязъл, първи излязъл", известна ощe като дисциплина FIFO ( Fifsl In, Fifst Out). Опашката се реализира чрез един масив, наречен buf, в който се съхраняват елементите на опашката и два индекса first и last, които са съответно номерът на първия и номерът на последния елемент в опашката. Променливата count съдържа текущия брой на елементите в опашката. Константата МАХ определя максималния брой елементи, които може да побере опашката. За да не се усложнява примерът, в програма 5.16 е реализирана опашка, чиито елементи са числа от тип int.
Работата с опашката се осъществява чрез три функции - put_last, get_first и clear, реализиращи съответно операциите вмъкване на елемент в опашката, извличане на елемент от опашката и изтриване на всички елементи от опашката (изпразване на опашката). Всички изброени функции и променливи са статични, т.е. областта на действието им е само файла pr0516a.cpp, в който те са дефинирани. Единствената функция, дефинирана в този файл pr0516a.cpp, която не е статична е функцията dispatch. Тя извиква една от функциите put_last, get_first или clear в зависимост oт стойността на параметъра com. Този параметър има смисъл на команда и се въвежда от клавиатурата във функцията main, която е дефинирана във файла pr0516.cpp и има достъп единствено до функцията dispatch.
Файлът pr0516а.cpp може да се разглежда като един модул, който има определена вътрешна структура и поведение (статичните променливи и функции) и интерфейс (функцията dispatch), чрез който се осъществява взаимодействието с външната среда. Вътрешната структура на модула е недостъпна за никоя функция, освен за функцията dispatch. По този начин се постигат два положителни ефекта:
- вътрешните данни на опашката (buf, first, last, count) са предпазени от некоректна работа с тях;
- потребителят спазва определена дисциплина за работа с опашката, което намалява възможностите за грешки.
5.12. ФУНКЦИИ, КОИТО ВРЪЩАТ УКАЗАТЕЛИ
Върнатите стойности на функциите могат да бъдат от произволен тип, включително и указатели. Ето как изглежда дефиницията на една функция с име f, която връща указател към тип с име type:
type *f(...........)
{
. . . . . . . . .
return ;
}
5.13. УКАЗАТЕЛИ КЪМ ФУНКЦИИ
В С++ и С могат да бъдат дефинирани указатели, чиито стойности не са адреси на данни, а начални адреси на функции. Такива указатели се наричат указатели към функции. Ето как изглежда дефиницията на един указател към функция:
float (*funcpoint)(int, float);
Тази дефиниция означава, че funcpoint е указател към функция, която има два параметъра от тип int и float и връща стойност от тип float. Обърнете внимание на скобите, ограждащи указателя funcpoint! Ако те липсват, дефиницията ще означава, че funcpoint е функция, която връща указател към float.
На указателите към функции могат да се присвояват началните адреси на различни функции, след което извикването на функциите да става чрез указателите. Началните адреси на функциите се означават чрез имената на функциите, без да се поставят скоби след тях и без да се задават параметри. Например, нека е дефинирана една функция с име f1:
float f1(int х, float y)
{
. . . . . . . . . . . .
}
Началният адрес на функцията f1 може да бъде присвоен на указателя funcpoint чрез израза:
funcpoint = f1;
От този момент, функцията f1 може да бъде извиквана чрез указателя funcpoint no следния начин:
(*funcpoint)(5, 3.4);
Някои компилатори, в това число и BORLAND C++, допускат извикването на функция чрез указател да става само чрез името на указателя, а не чрез съдържанието на указателя, поставено в скоби. Имайки предвид това, вместо (*funcpoint) (5, 3.4), може да се използува по-простият запис funcpoint(5,3.4).Чрез указателя funcpoint могат да бъдат извиквани и други функции, като преди това трябва да му бъдат присвоявани техните начални адреси.
Указателите към функции намират приложение предимно в два случая. Първият случай е свързан с разработването на драйвери за различни устройства, с които работи дадена програма. Драйверите се оформят като отделни функции, а използуването им от програмата става чрез указатели към функции. По този начин се постига независимост на програмата от конкретните драйвери в даден момент. Освен това добавянето на нови драйвери не изисква промени в програмите, които ги използуват. Вторият, често срещан случай на използуване на указатели към функции, е свързан с предаването на функции като параметри на други функции. За да може една функция да се предаде като параметър на друга функция, то съответният формален параметър трябва да бъде указател към функция. Програма 5.18 илюстрира този случай.
Програма 5.18. Табулация на функция. Предаване на функция като параметър
#include
//Дефиниция на функция tabul
void tabul ( float (*f) (float), float y[], float x[], int count, float step = 1 )
{
for ( int i = 0; i < count; i++ ) y[i] = (*f)( x(i] = i*step );
}
//Дефиниция на функция f1
float f1 ( float х )
{
return x*x+2;
}
//Дефиниция на функция f2
float f2 ( float х )
{
return x*x-l;
}
//Дефиниция на функция main
main()
{
const MaxLen = 5; //Максимална дължина на масивите
float Х[MaxLen], y[MaxLen];
tabul ( f1, y, х, MaxLen, 2 );
cout << "РЕЗУЛТАТ 1:\n";
for ( int i = 0; i < MaxLen; i++ ){
cout << "x[" << i << "]=" << x[i] << " ";
cout << "y[" << i << "]=" << y[i] << '\n';
}
tabul ( f2, y, x, MaxLen );
cout << "РЕЗУЛТАТ 2:\n";
for ( i = 0; i < MaxLen; i++ ) {
cout << "x[" << i << "]=" << x[i] << " ";
cout << "y[" << i << "]=" << y[i] << '\n';
}
}
Функцията tabul осъществява табулация на функция (математическа) на един аргумент. Функцията, която се табулира, се предана като първи параметър на функцията tabul. След изпълнение на функцията tabul, масивите у и х съдържат съответно стойностите на функцията, която се табулира (предава се като първи параметър на функцията tabul) и стойностите на аргумента, за които е изчислена функцията. Параметърът count съдържа броя на стойностите в масивите у и х. В програма 5.18 масивите са дефинирани във функцията main, като дължината им е зададена чрез константата MaxLen. Параметърът step задава стъпката на изменение на стойностите на аргумента на функцията, която се табулира. Параметърът step има подразбираща се стойност единица. Следователно, ако при извикване на функцията tabul параметърът step не бъде зададен, то неговата стойност ще бъде едно (виж т. 5.14.). Във функцията main, функцията tabul се извиква два пъти, за да се табулпрат две функции с имена f1 и f2. функциите f1 и f2 съответствуват на две математически функции, които имат следните аналитични записи: f1(x) = х*х+2 и f2(x) = х*х-1. При първото извикване на функцията tabul параметърът step има стойност две. При второто извикване параметърът step не се предава. Следователно функцията f2 ще бъде табулирана със стъпка единица, колкото с подразбиращата се стойност на параметъра step.
Някои компилатори, в това число и BORLAND C++, допускат опростено деклариране на указателите към функции като формални параметри на функциите. Опростяването се състои в премахване на скобите и * пред имената на указателите към функции. Следвайки това правило, функцията tabul може да бъде дефинирана и по този начин:
void tabul(float f(float),float y[],float x[].int count,float step=1)
{
for ( int i = 0; i < count; i++ )
y[i] = f( x[i] = i*step );
}
5.14. ФУНКЦИИ С ПОДРАЗБИРАЩИ СЕ ПАРАМЕТРИ
Функциите в С++ могат да имат подразбиращи се параметри, т.е. параметри, на които са зададени подразбиращи се стойности. За задаване на подразбиращи се стойности на параметрите се използува знака за присвояване. Ето един пример:
float func ( int х, float у = 2.3, char *s = "string1" )
{
cout << "x=" << х << " y=" << у << " s=" << s << ' \n';
}
Параметрите у и s на функцията func са подразбиращи се и техните подразбиращи се стойности са съответно 2.3 и "string1". Тези стойности ще бъдат предадени на функцията като параметри, ако при нейното извикване не бъдат зададени други стойности на подразбиращите се параметри. Тъй като функцията func има два подразбиращи се параметъра, тя може да бъде извикана по три различни начина - с един, с два и с три параметъра. Например,
func ( 2 );
func ( 2, 3.0 );
func ( 2, 4.0, "string2" );
Резултатите от- тези три изпълнения на функцията func ще бъдат съответно:
х=2 у=2.3 s= string1
х=2 у=3.0 s= string1
х=2 у=4.0 s= string2
При използуване на функции с подразбиращи се параметри трябва да се има предвид следното правило: Ако един параметър на дадена функция е подразбиращ се, то всички параметри, които се намират след него в списъка на параметрите също трябва да бъдат подразбиращи се. Следователно дефиниция от вида:
int f ( int х, float у = 1.2, char *s ) //ГРЕШКА!
{
.............
}
е некоректна, тъй като вторият паряметър у е подразбиращ се, а параметърът s, които се намира след него в списъка на формалните параметри, не е подразбиращ се.
5.16. РЕКУРСИВНИ ФУНКЦИИ
Рекурсията е механизъм, при които една функция може да извика сама сeбe си. Функциите, които използуват рекурсия, се наричат рекурсивни. Има два варианта на рекурсия, наречени пряка и непряка (косвена) рекурсия. Пряка рекурсия има тогава, когато в тялото на дадена функция има извикване на същата тази функция, т.е. функцията пряко вика себе си. Косвена рекурсия има тогава, когато една функция извиква друга функция, а тази друга функция на свой ред извиква първата. Косвената рекурсия може да бъде и на повече нива, например една функция вика втора функция, втората функция вика трета функция, а третата функция вика отново първата функция.
Рекурсивните функции са особено подходящи, когато трябва да се реализират алгоритми, които са рекурсивни по определение. Типични примери за такива алгоритми са изчисляване на факториел, изчисляване на числата на Фибуначи, обработка на дървовидни структури данни и др. Като пример за рекурсивна функция ще разгледаме изчисляването на n!. То се извършва по известната формула:
1, aкo n=0 или n=1
n! =
n*(n-1)!, акo n>1
Тази формула се реализира от рекурсивната функция fact, дефинирана по следния начин:
fact ( int n )
{
if ( n == 0 || n == 1 ) return 1;
return n * fact(n-l); //Buкa себе си, т.e. пряка рекурсия
}
Докaзано е, че всяка рекурсивна функция има итеративен вариант, т.е. всеки рекурсивен алгоритъм може да се реализира без използуване на рекурсивни функции. Препоръчваме на читателите да напишат итеративна функция, която да изчислява n! и да я сравнят с рекурсивната функция fact, показана по-горе.
В основата на рекурсията са автоматичните променливи, които се създават при всяко извикване на дадена функция. Ако няма автоматични променливи, то ново извикване на дадена функция преди завършване на нейното предидущо извикване би предизвикало разрушаване на данните, които са необходими за завършване на предидущото извикване. Ще поясним този проблем на базата на функцията fact. Функцията fact има един параметър n, който е автоматична променлива. Нека функцията fact бъде извикана с фактически параметър две: fact(2);. При извикването в стека ще бъде заделена памет за параметъра n и в тази памет ще се запише стойността 2. Тъй като условието на оператора if не е изпълнено (n е 2), ще се достигне до оператора return n*fact(n-l);. Неговото изпълнение изисква изчисляване на израза n*fact(n-l);. Следователно отново ще бъде извикана функцията fact, но с параметър, чиято стойност е n-1, т.е. 1. При това второ извикване на функцията fact в стека ще бъде заделена нова памет за нейния параметър, която е различна от паметта, заделена за параметъра на функцията при нейното първо извикване. Следователно параметърът n от първото извикване на функцията запазва своята стойност, която е 2. При второто извикване на функцията условието на оператора if ще бъде изпълнено, тъй като n има стойност 1. Следователно ще бъде изпълнен оператора return 1;, с което ще завърши изпълнението на второто извикване на функцията fact. Тогава ще продължи изпълнението на оператора return от първото извикване на функцията. Върнатата стойност от второто извикване на функцията fact, която е 1, ще бъде умножена по стойността на параметъра n от първото извикване на функцията, която е 2 и полученият резултат, който е 2, ще бъде резултатът от извикването на функцпята fact(2).
Ако параметърът n не е автоматична променлива, т.е. ако при всяко извикване на функцията не се заделя нова памет за него, функцията fact няма да работи правилно, тъй като стойността на n от първото извикване на функцията Ще бъде променена след нейното второ извикване. Това ще доведе до неверен резултат от изчислението на израза, зададен в оператора return.
Рекурсивните функции като правило са по-компактни като запис от итеративните, но зад компактния запис е скрит сложния механизъм на рекурсията, което затруднява анализа им. Трябва да се има предвид още, че рекурсивните функции изискват повече памет и в много случаи са по-бавни от итеративните. Поради тези причини трябва внимателно да се анализират предимствата и недостатъците от използуването на рекурсивни функции за всеки отделен случай.
Ще илюстрираме използването на рекурсивни подпрограми с няколко примеи:
Програма 5.20 Програма с рекурсивна функция за пресмятане на N!
#include
#include
// Дефиниране на функцията swop
int fakt (int N)
{
if (N==0||N==1) return 1;
return N*fakt(N-1);
}
// Дефиниране на функцията main
main()
{
int M,Z;
coutM;
Z=fakt(M);
cout<
getch();
}
Програма 5.25. Програма с рекурсивна функция за сумиране елементите на едномерен масив
#include
#include
int arr[5];
// Дефиниране на функцията sum_arr
int sum_arr (int n)
{
if (n==0) return arr[0];
return arr[n] + sum_arr(n-1);
}
// Дефиниране на функцията main
main()
{
int i,s,N;
coutN;
cout<<"Задайте елементите на масива\n";
for (i=0;i
cout<<"i="<
}
s=sum_arr(N-1);
cout<<"Сумата на елементите е:"<
getch();
}