Начало Решаване на проблеми



страница8/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   4   5   6   7   8   9   10   11   ...   19
Раздел 1.8 (стр. 51) съдржа дефиницията на класа IntArray.

3.8. Аргумент - масив


Масивите в С++ никога не се изпращат по стойност.

По-скоро масивът се подава чрез указател към първия му

елемент. Например,
void putValues( int[ 10 ] )]
се разглежда от компилатора сякаш е било декларирано като
void putValues( int* );
Безмислено е да се декларира размера на масив, когато се

подава като формален аргумент. Следните три декларации са

еквивалентни:
// three equivalent declarations of putValues

void putValues( int* );

void putValues( int[] );

void putValues( int[ 10 ] );


За програмиста това означава следните две неща:
- Промените на аргумента - масив в извиканата функция

са направени над фактическия масив на обръщението, а

не над локалното копие. В случаите, когато масивът

на обръщението трябва да остане непроменен, е

необходимо програмистът сам да симулира изпращане по

стойност.


- Размерът на масива не е част от типа на аргумента.

Функцията, която има аргумент масив не знае неговият

фактически размер; това важи и за компилатора. Няма

проверка за размера на масива. Например,


void putValues( int[ 10 ] ); // treated as int*

main()


{

int i, j[ 2 ];

putValues( &i ); // ok: int*; run-time error

putValues( j ); // ok: int*; run-time error

return 0;

}
Проверката на типа на аргумента просто потвърждава, че двете

обръщения към putValues() са с аргумент от тип int*.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

123.
Съществува споразумение, че низов масив от символи се

ограничава с нулев символ. Всички останали типове на масиви,

обаче, включително символните масиви, които желаем да

обработим с вмъкнатите нулеви символи, трябва да указват по

някакъв начин размера си, когато бъдат изпращани като формални

аргументи на функции. Един общ метод е да използуваме

допълнителен аргумент, който съдържа размера на масива.
void petValues9 int[], int size );

main()


{

int i, j[ 2 ];

putValeus( i, 1 );

putValues( j, 2 );

return 0;

}
putValues() отпечатва стойностите на масива в следния формат:


( 10 ) < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >
където 10 представлява размера на масива. Ето една реализация:
#include

const lineLength = 12; // elements to a line

void putValues( int *ia, int sz )

{

cout << "(" << sz; ") <";



for ( int i = 0; i < sz; ++i )

{

if ( i % lineLength == 0 && i )



cout << "\n\t"; // line filled

cout << ia[ i ];


// seperate all but last element

if ( i % lineLength != lineLength-1 &&

i != sz - 1 )

cout << ", ";

}

cout << " >\n";



}
Многомерните масиви, декларирани като формални аргументи,

трябва да определят всички свои размерности след първата.

Например,
void putValues( int matrix[][10], int rowSise );
декларира matrix като двумерен масив. Всеки ред на matrix се

състои от 10 елелента. Една еквивалентна декларация на matrix

има вида
int (*matrix) [10];
Това декларира matrix като указател към масив от 10 елемента.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

124.
matrix += 1;
ще увеличи matrix с размера на втората му размерност и ще го

насочи към следващия му ред - ето защо трябва да бъде

поддържана следващата размерност. Заграждането на matrix в

скоби е необходимо, защото индексният оператор има по-висок

приоритет.
int *matrix[ 10 ];
декларира matrix като масив от десет указателя към цели числа.

3.9. Програмен обхват


По рано беше отбелязано, че всеки идентификатор в

програмата трябва да бъде уникален. Това не означава, обаче,

че едно име може да бъде използувано само веднъж в програмата:

името може да бъде използувано повторно при условие, че

съществува някакъв контекст, според който да бъдат

разграничавани различните представители. Един такъв контекст е

сигнатура на функция. В последния раздел, например,

putValues() се използува като име на две функции. Всяка от тях

има уникална разграничителна сигнатура.

Един втори, по-общ контекст е обхвата. С++ поддържа

три вида обхват: файлов, локален и класов. Файловият обхват се

отнася за тази част от текста на програмата, който не се

съдържа във функция или дефиниция на клас. Той представлява

най-външният обхват на програмата. Може да се каже, че той

обхваща както локалния, така и класовия обхват.

Най-общо, локалният обхват се свързва с тази част от

текста на програмата, която се съдържа в дефницията на

функцията. Счита се, че всяка функция има ясно дефиниран

локален обхват. В тялото на функцията за всеки съставен

оператор (или блок), съдържащ един или повече декларативни

оператори, се поддържа определен локален обхват. Локалният

обхват на даден блок може да бъде вложен. Списъкът от

аргументи се разглежда като принадлежащ на локалния обхват на

функцията. Не може да се каже, че списъкът от аргументи

принадлежи на файловият обхват.

Рааздел 5.8 (стр 217) представя подробно обсъждане на

класовият обхват. Съществуват две важни бележки, които ще

направим сега:


1. Счита се, че всеки клас представя ясно разграничен

класов обхват.


2. Членовете - функции се разглеждат като включени в

обхвата на класа.


От езика се гарантира, че вкяка глобална променлива,

която не е инициализирана явно получава стойност 0. По такъв

начин при следните две дефиниции променливите i и j получават

начална стойност 0:


int i = 0;

int j;
Стойността на една неинициализирана локална променлива не е

дефинирана.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

125.
Дадено име може да бъде използувано повторно когато

е налице разлика в обхвата. Например, в следния пограмен

фрагмент има четири уникални представители на ia:
void swap( int *ia, int, int);

void sort( int *ia, int sz );

void putValues( int *ia, int sz );
int ia[] = { 4, 7, 0, 9, 2, 5, 8, 3, 6, 1 };
const SZ = 10;

main()


{

int i, j;


// ...

swap( ia, i, j );

sort( ia, SZ );

putValues( ia, SZ );

}
С всяка променлива се свързва някакъв обхват, който

заедно с името й я идентифицира уникално. Променливата и

видима за програмата само в рамките на обхвата си (иначе

казано в областта си на действие). Една локална променлива,

например, е достъпна само във функцията, където е дефинирана -

това е причината, поради която името й може да бъде

използувано повторно в рамките на друг обхват без да възникне

конфликт.

Променлива, дефинирана в рамките на файлов обхват, е

видима в цялата програма. Има три случая, при които видимостта

й се подтиска:
1. Една локална променлива може да използува име на

глобална променлилва. Тогава се казва, че

глобалната променлива е скрита от локалния

представител. Как програмистът може да получи

достъп до скритата глобална променлива? Едно

решение предлага оператора за обхват (scope -

оператор).
2. Нека една променлива е дефинирана в даден файл, но

се използува също и в друг или множество други

файлове. Как програмистът декларира променливата в

тези файлове? Едно решение предлага ключовата дума

extern.
3. Нека две глобални променливи са дефинирани в

различни програмни файлове и имат еднакви имена, но

възнамеряваме да ги използуваме като различими

обекти. Всяка от тях сама по себе си се компилира

нормално. Когато ги компилираме заедно, обаче, се

отбелязва, че са дефинирани неколкократно и се

прекъсва процеса на компилация. Как програмистът

може да компилира програмата без да му се налага

да променя всички представители на една от

променливите? Използуването на ключовата дума

ststic предлага едно решение.
Тези три случая са обект на обсъждане в следващите три

раздела.


ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

126.
Оператор за обхват (scope)
Операторът за обхват предлага едно решение на проблема

за достъп до скрита глобална променлива. Идентификатор,

предществуван от оператор за обхват, ще ви осигури достъп до

глобален представител. В следващия пример, проектиран да

илюстрира как може да бъде използуван оператора за обхват,

една функция изчислява числата на Фибуначи. В нея са зададени

две дефиниции на променливата max. Глобалният представител на

променливата съдържа максималната стойност за числата от

поредицата. Локалният й представител показва желаната дължина

на редицата. (Да припомним, че формалните аргументи на една

функция се включват в локалния й обхват). Двата представителя

на max трябва да бъдат достъпни във функцията. Всяко явно

обръщение към max, обаче, ще бъде отнесено към локалния

представител. За да имаме достъп до глобалния представител

трябва да използуваме оператора за обхват - ::max. Ето една

реализация:


#include
const max = 65000;

const lineLength = 12;


void fibonacci( int max )

{

if (max < 2 ) return;



cout << "0 1 ";
for ( int i = 3, v1 = 0, v2 = 1, cur;

i << max; ++i)

{

cur = v1 + v2;



if ( cur > ::max ) break;

cout << cur << " ";

v1 = v2;

v2 = cur;

if ( i % lineLenggth == 0 )

cout << "\n";

}

}
Ето и една реализация на main(), отнасяща се до примерната



функция:
#include

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.


Каталог: files -> tu files
tu files -> Увод в компютърната графика
tu files -> Xii. Защита и безопасност на ос
tu files -> Електрически апарати
tu files -> Средства за описание на синтаксиса
tu files -> Stratofortress
tu files -> Писане на скриптове за bash шел : версия 2
tu files -> 6Технологии на компютърната графика 1Модели на изображението
tu files -> Z=f(x), където x- входни данни; z
tu files -> Body name библиотека global Matrix imports (достъп по име) … var m[N, N] := … end decl., proc … resource f final code imports node, Matrix end name var x: node node; if x … Matrix m[3,4] :=: … end


Сподели с приятели:
1   ...   4   5   6   7   8   9   10   11   ...   19




©obuch.info 2022
отнасят до администрацията

    Начална страница