void fibonacci( int );
main()
{
cout << "Fibonacci Series: 16\n";
fibonacci( 16 );
return 0;
}
Когато компилираме и изпълним тази програма ще получим следния
резултат:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
127.
Fibonacci Series: 16
0 1 1 2 3 5 8 13 21 34 55 89
144 233 337 610
Променливи extern
Ключовата дума extern предлага един метод за
деклариране на променлива без да я дефинираме. По подобие на
прототипа на функция тя указва, че някъде в програмата е
дефиниран идентификатор от този тип.
extern int i;
дава "обещание", че някъде в програмата съществува дефиниция
от вида:
int i;
Декларацията extern не предизвиква отделяне на памет.
Тя може да бъде писана многократно в програмата. Обикновено,
обаче, тя се записва еднократно в публичен заглавен файл за да
бъде включвана, когато е необходимо.
Ключовата дума extern може да бъде записвана и в
прототип на функция. Единственият ефект на това ще бъде, че
неявната й характеристика "дефинирана другъде" ще стане явна
за прототипа. Това се прави по следния начин:
extern void putValues( int* , int );
Декларацията на променлива extern в явен инициализатор се
разглежда като дефиниция на тази променлива. Отделя се памет и
всяка следваща дефиниция на тази променлива в същия обхват се
отбелязва като грешна. Например,
extern conts bufSize = 512; // definition
Глобални променливи static
Глобална променлива, която е предшествана от ключовата
дума static ще бъде невидима вън от файла, в който е
дефинирана. Тези променливи и функции, които се отнасят само
за файла, в който са дефинирани, се декларират като static. По
този начин те не могат да влязат в конфликт с глобалните
идентификатори в други файлове, в които случайно се използуват
същите имена.
За идентификаторите static, които имат файлов обхват,
се казва, че притежават вътрешно свързване. (За глобалните
идентификатори, които не са static се казва, че притежават
вътрешно свързване). По подразбиране, дефинициите на функции
inline и константи са static.
Ето един пример за това кога една функция може да бъде
декларирана като static. Тя съдържа три функции: bsort(),
qsort() и swap().
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
128.
Предназначението на bsort() и qsort() е да сортират масив във
възходящ ред. Функцията swap() се вика и от двете функции, но
не са предназначени за обща употреба. За нашия пример те са
декларирани като staic. (Друга алтернатива е да бъдат
декларирани като inline).
static void swap ( int *ia, int i, int j )
{ // swap two elements of array
int tmp = ia[ i ];
ia[ i ] = ia[ j ];
ia[ j ] = tmp;
}
void bsort( int *ia, int sz )
{ // bubble sort
for ( int i = 0; i < sz; i++ )
for ( int j = i; j < sz; j++ )
if ( ia[ i ] > ia[ j ] )
swap( ia, i, j );
}
1 void qsort( int *ia, int low, int high )
2 { // stopping condition for recursion
3 if ( low < high )
{
4 int lo = low;
5 int hi = high + 1;
6 int elem = ia[ low ];
7
8 for (;;)
{
9 while ( ia[ ++lo ] <= elem );
10 while ( ia[ --hi ] > elem );
11
12 if ( lo < hi )
13 swap( ia, lo, hi );
14 else break;
15 } // end, for(;;)
16
17 swap( ia, low, hi - 1 );
18 qsort( ia, low, hi - 1 );
19 qsort( ia, hi + 1, high );
20 } // endq if ( low < high );
21 }
qsort() е една реализация на алгоритъма за бързо
сортиране на C.A.R Hoare. Нека разгледаме подробно тази
функция. low и high задават долната и горната граница на
масива. qsort() е една рекурсивна функция, която прилага към
себе си прогресивно намаляващи подмасиви. Условието за
приключване на работа е когато долната граница е равна (или
по-голяма) от горната граница (ред 3).
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
129.
elem (ред 6) се нарича разделящ елемент. Всички
елементи на масива, които са по-малки от elem се преместват от
лявата му страна; а всички по-големи елементи - отдясно. Сега
масивът е разделен на два подмасива. qsort() се прилага
рекурсивно към всеки от тях (редове 18 - 19).
Предназначението на цикъла for(;;) е да извърши
разпределянето на елементите (редове 8 - 15). При всяка
итерация на цикъла lo се увеличава, докато не достигне индекса
на първия елемент на ia, който е по-голям от elem (ред 9).
Съответно hi се намалява, докато не достигне най-крайния
индекс на елемент, който е по-малък или равен на elem (ред
10). Ако lo вече не е по-малко от hi елементите са били
разпределени и ние прекъсваме изпълнението на цикъла; иначе
елементите се разменят и започва следващата итерация (редове
12 - 14).
Въпреки, че масивът е бил разпределен, elem все още се
намира в ia[low]. Функцията swap() от ред 17 го поставя на
окончателната му позиция в масива. qsort() се прилага тогава
към двата подмасива.
Следната реализация на main(), която изпълнява двете
сортиращи функции, използува putValues() при отпечатването на
масива.
#include
#include "sort.h" /* bsort(), qsort() */
#include "print.h" /* putValues() */
// for illustration, predefine arrays
int ia1 [ 10 ] = { 26, 5, 37, 1, 61, 11, 59, 15,
48, 19 } ;
int ia2 [ 16 ] = { 503, 87, 512, 61, 908, 170, 897,
275, 653, 426, 154, 509, 612, 677, 765, 703 } ;
main()
{
cout << "\nBubblesort of first array";
bsort( ia1, 10 );
putValues( ia1, 10 );
cout << "\nQuicksort of second array";
qsort( ia2, 0, 15 );
putVaalues( ia2, 16 );
}
Когато компилираме и изпълним тази програма ще получим следния
резултат:
Bubblesort of first array
( 10 ) < 1, 5, 11, 15, 19, 26, 37, 48,
59, 61 >
Quicksort of second array
( 16 ) < 61, 87, 154, 170, 275, 426, 503, 509,
512, 612, 653, 677, 703, 897, 908 >
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
130.
3.10. Локален обхват
Най-общо казано, за локалните променливи се отделя
памет при обръщението към функцията; за тях се казва, че
"идват във" или че имат "входен обхват". Тази памет се отделя
по време на изпълнение от програмния стек и е част от записа
за активиране на функцията. Една неинициализирана локална
променлива ще съдържа случайна стойност, според битовата
последователност, записана в паметта при предишното й
използуване. За тази стойност се казва, че не е дефинирана.
Когато една функция приключи работата си, от
програмния стек се отстранява записа й за активиране. По такъв
начин паметта, свързана с локалните променливи се освобождава.
Казва се, че променливите "излзат вън" или, че имат "изходен
обхват" при приключване на изпълнението на функцията.
Стойностите, които те съдържат, се загубват.
Понеже паметта, отделена за локалните променливи, се
освобождава, когато функцията приключи изпълнението си, никога
адрес на локална променлива не бива да бъде изпращан вън от
обхвата й. Ето един пример:
#include
const strLen = 8;
char *globalString;
char *trouble()
{
char localString[ strLen ];
cout << "Enter string: ";
cin >> localString;
return localString; // dangerous
}
main() { globalString = trouble(); ... }
globalString съдържа адреса на локален масив от символи -
localString. За нещастие, обаче, паметта, отделена за
localString се освобождава, когато функцията trouble()
приключи изпълнението си. Когато се върнем отново в main(),
globalString фактически адресира неизползувана памет.
Когато адресът на дадена локална променлива се изпраща
вън от обхвата й се казва, че става дума за "висящ указател".
Това е една сериозна програмна грешка, понеже съдържанието на
адресираната памет е непредсказуемо. Ако битовете на този
адрес се окажат сравнително подходящи, програмата може да се
изпълни до край, но да даде грешни резултати.
Локалните обхвати могат да бъдат влагани. Всеки блок,
който съдържа декларативни оператори поддържа свой собствен
локален обхват. Например, следната част от програма дефинира
две нива на локален обхват, реализирайки двоично търсене в
сортиран масив.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
131.
const notFound = -1;
int binSearih( int *ia, int sz, int val )
{ // local scope: level #1
// contains ia, sz, val, low, high
int low = 0;
int high = sz - 1;
while ( low <= high )
{ // local scope: level #2
int mid = ( low + high )/2;
if ( val == ia[ mid ] )
return mid;
if ( val < ia[ mid ] )
high = mid - 1;
else low = mid + 1;
} // end, local scope: level #2
return notFound;
} // end, local scope: level #1
Цикълът while от binSearch() дефинира вложен локален обхват.
Той съдържа един идентификатор, идентификатора mid, затворен в
локалния обхват на binSearch(). Той съдържа аргументите ia,
sz и val, както и локалните променливи high и low. Глобалният
обхват затваря и двата локални обхвата. Той съдържа един
идентификатор, цялата константа notFound.
Когато е създаден псевдоним на даден идентификатор се
търси непосредственият обхват, в който се явява псевдонима.
Ако е намерена дефиниция на идентификатора, псевдонимът се
свързва с нея; иначе се търси в по-голям обхват. Този процес
продължава или докато псевдонимът се свърже с идентификатора
или докато бъде изчерпан и глобалния обхват. Ако се случи
последното, псевдонимът се отбелязва като неправилно
дефиниран. Използуването на оператора за обхват ("::")
огаранечава търсенето да глобален обхват.
Един цикъл for разрешава дефинирането на променливи в
управляващата си структура. Например,
for ( int i = 0; i < arrayBound; ++i )
Променливите, дефинирани в управляващата част на цикъла for,
се включват в същия обхват, в който е и самият оператор for.
Нека операторът for е записан така:
int i;
for ( i = 0; i < arrayBound; ++i )
Това осигурява достъп на програмиста до управляващите
променливи след приключване на изпълнението на цикъла.
Например,
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
132.
const notFound = -1;
findElement( int *ia, int sz, int val )
{
for ( int i = 0; i < sz, ++i )
if ( ia[ i ] == val ) break;
if ( i == sz ) return notFound;
return i;
}
Това не разрешава на програмиста, обаче, да използува отново
имената на управляващите променливи в същия обхват. Например,
fooBar( int *ia, int sz )
{
for (int i=0; i
for (int i=0; i
for (i=0; i
}
Локални променливи static
Желателно е даден идентификатор да се декларира като
локална променлива навсякъде, където използуването му е
ограничено във функция или вложен блок. Когато стойността на
тази променлива трябва да остане след извикването, обаче,
обикновените локални променливи не могат да бъдат използувани.
Тяхната стойност ще изчезва всеки път, когато бъде напуснат
обхвата й.
Този проблем може да бъде решен чрез деклариране на
идентификатора като static. За всяка локална променлива static
се отделя постоянна памет. Нейната стойност остава след
извикването; но достъпът до нея остава ограничен в локалния й
обхват. Например, ето една версия на gcd(), която проследява
дълбочината на рекурсията, използувайки локална променлива
static:
#include
traceGcd( int v1, int v2 );
{
static int depth = 1;
cout << "depth #" << depth++ << "\n";
if (v2 == 0 )
return v1;
return traceGcd( v2, v1%v2 );
}
Стойността, свързана със static локалната променлива depth
остава след извикването на traceGcd(). Инициализация се
извършва само веднъж. Следната малка програма показва
изпълнението на traceGcd():
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
133.
#include
extern traceGcd(int, int);
main()
{
int rslt = traceGcd( 15, 123 );
cout << "gcd of (15, 123): " < rslt << "\n";
}
Когато компилираме и изпълним тази програма ще получим следния
резултат:
depth #1
depth #2
depth #3
depth #4
gcd of (15,123): 3
Локални променливи register
Локални променливи, които се използуват често в дадена
функция, могат да бъдат определени с ключовата дума register.
Ако е възможно, компилаторът ще зареди променливата в някой
машинен регистър. Ако не е възможно, променливата остава в
паметта. Очевидни кандидати за променливи register са
индексите на масивите:
for ( register int i = 0; i < sz; ++i )
ia[ i ] = i;
Формалните аргументи могат също да бъдат декларирани като
променливи register:
class iList
{
public:
int value;
iList *next;
};
int find( register iList *ptr, int val )
{ // find vala in ilnked list
while ( ptr )
{
if ( ptr->value == val )
return 1;
ptr = ptr->next;
}
return 0;
}
Променливите register могат да увеличат скоростта на
изпълнение на функцията ако избраните променливи се използуват
често.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
134.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
135.
Сподели с приятели: