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



страница12/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   8   9   10   11   12   13   14   15   ...   19
linearScreenDump
Операторът за изход може да бъде реализиран по следния начин:
ostream& operator<<( ostream& os, Screen& s )

{

os << "\n<" << s.heigth



<< "," << s.width << ">";
char *p = s.screen;

while ( *p ) os.put( *p++ );


return os;

}
Ето пример за използуване на оператора за изход. main() се

реализира така:
#include

#include "Screen.h"


main()

{

Screen x(4,4,'%');



cout << x;

return 0;

}
Когато компилираме и изпълним тази програма получаваме:
<4,4>%%%%%%%%%%%%%%%%
Операторът за вход ше чете като входни данни резултата

от изпълнението на оператора за изход на Screen. Нека запазим

изходния резултат във файл с име output. Реализацията на

операторът за вход е представена по нататък; проверката на

формата на входния текст е пропусната за да се спести място.

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


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

203.

istream& operator>>( istream& is, Screen& s )



{ // read Screen object output by operator <<

int wid, hi;

char ch;
// format verification not shown

// screenDump

is >> ch; // '<'

is >> hi; // get heigth

is >> ch; // ','

is >> wid; // get width

is >> ch; // // '>'
delete s.screen;
int sz = hi * wid;

s.height = hi; s.width = wid;

s.cursor = s.screen = new char[ sz + 1 ];
char *endptr = s.screen + sz;

char *ptr = s.screen;

while ( ptr != endptr ) is.get( *ptr++ );

*ptr = '\0';


return is;

}
Следната малка програма дава пример за използване

както на оператора за вход, така и на оператора за изход

за класа Screen.


#include

#include "Screen.h"


main()

{

Screen x(5,5,'?');



cout << "Initial Screen: \t" << x;
cin >> x;

cout << "\nInput Screen: \t" << x;


return 0;

}
Входните данни са като изхода на предходната програма. Когато

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

резултат:

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

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

204.
Initial Screen:

<5,5>??????????????????????????

Input Screen:



<4,4>%%%%%%%%%%%%%%%%
Упражнение 5-8. Операторът за вход трябва да проверява

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

че това да бъде изпълнено.
Упражнение 5-9. Реализирането на оператора за изход

така, че да запазва позицията на курсора не е трудно. За това

могат да послужат член функциите col() и row(). Напишете

отново оператора за изход, така че да запазва текущата позиция

на курсора.
Упражнение 5-10. Напишете отново оператора за вход,

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

предходоното упражнение оператор за изход.
Един клас трябва да определи всеки представител на

дадена презаредима функция, която желае да бъде приятел на

класа. Например,
iostream& storeOn( iostream&, Screen& );

BitMap& storeOn( BitMap&, Screen& );


class Screen

{

friend iostream&



storeOn( iostream&, Screen& );

friend BitMap&

storeOn( BitMap&, Screen& );

public:


// ... rest of the Screen class

};
Ако дадена функция обработва обекти от два различни класа, тя

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

член функция на единия клас и приятел на другия. Нека да

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

В първия случай функцията логически трябва да бъде

член на двата класа. Понеже това е невъзможно вместо това

функцията става приятел на и двата класа. Например,

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

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

205.
// forward declarations

class Screen;

class Window;
Screen& isEqual( Screen&, Window& );
class Screen

{

friend Screen& isEqual( Screen&, Window& );



// ... rest of Screen goes here

};
class Window

{

friend Screen& isEqual( Screen&, Window& );



// ... rest of Window goes here

};
Във втория случай функцията логически принадлежи като

член на единия клас. Освен това, обаче, й е необходим достъп

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

приятел на втория клас:
class Window;

class Screen

{

public:


Screen& copy( Window& );

// ... remaining Screen members

};
class Window

{

friend Screen& Screen::copy( Window& )



// ... remaining Window members

};
Screen& Screen::copy( Window& ) { /* ... */ }


Цял един клас може да бъде деклариран като приятелски

на някой клас. Например,


class Window;

class Screen

{

friend class Window;



public:

// ... rest of the Screen class

};
Сега непубличните членове на класа Screen са достъпни

за всички член функции на Window.

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

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

206.
5.6. Статични членове на класа
Понякога е необходимо всички обекти на даден клас да

имат достъп до определена променлива. Това може да бъде

някакъв флаг за условие или брояч, свързан с класа, който се

променя динамично от програмата - може един брояч да отразява

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

програмата. Понякога просто е по-ефективно да се зададе една

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

да обработва свое собствено копие. Тази променлива може да

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

се до класа или указател към свободната памет на класа. Едно

решение за тези случаи се предлага от статичните членове на

класа.


Всеки статичен член данни работи като глобална

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

глобалните променливи имат две допълнителни характеристики:
1. Може да бъде предизвикано скриване на информация.

Статичните член данни може да бъдат дефиниран като

непублични; глобалните променливи не могат.
2. Статичните членове не влизат в глобалното

"пространство на имената" на програмата и поради

това се отстранява възможността за случаен конфликт

между имената.


За даден клас съществува само един представител на

статичния член данни. Той е дефиниран в обхвата на класа; т.е.

винаги, когато дефиницията на класа е достъпна. Статичният

член данни е единичен обект, достъпен за всички обекти на

класа си.

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

към декларацията му бъде добавена ключовата дума static.

Статичните член данни се подчиняват на правилата за достъп

public/private/protcted.

Например, в дефинираният по-нататък клас CoOp

costPerShare е декларирана като личен статичен член от тип

double.
class CoOp

{

friend compareCost( CoOp&, CoOp* );



public:

CoOp( int, char* );

inline double monthlyMaint();

void raiseCost( double incr )

{ cosrPerShare += incr ); }

double getCost() { return costPerShare; }

private:

static double costPerShare;

int shares;

char *owner;

};
С решението да бъде направена costPerShare static се

целят две неща: да се запазва стойността й и да се ограничат

възможностите за грешки. Всеки CoOp обект трябва да има достъп

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


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

207.
до costPerShare. Въпреки, че нейната текуща стойност е една и

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

тя не може да бъде дефинирана като константа. Но понеже не

ефикасно всеки обект от този клас да обработва копие на тази

стойност ние я декларираме като static. След като costPerShare

е static, с еднократното й изменение ние сме уверени, че всеки

обект от класа има достъп до една и съща стойност. Ако всеки

обект на класа имаше собствено копие, то /копието/ трябваше да

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

Всеки статичен член данни се инициализира вън от

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

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

използуван синтаксиса на оператора за обхват на класа.

Например, ето как бихме могли да инициализираме costPerScope:


#include "CoOp.h"

double CoOp::costPerShare = 23.99;


Както променливите нечленове, така и статичните член

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

Това означава, че инициализирането на статичните член данни

трябва да бъде поставяно във файл заедно с дефинициите на член

функциите, които не са inline, а не в заглавния файл на класа.

Нивото на достъп до един статичен член данни се отнася само до

достъпа за четене и запис на този член, а не до

инициализацията му. Ето защо costPerShare може да бъде

инициализиран само във файлов обхват.

Достъпът до един статичен член на клас е същия, както

и достъпът да статичен член. Например,
inline double

CoOp::monthlyMaint()

{

return( costPerShare * shares );



}
// Pointer and Reference arguments in order to

// illustrate object and pointer access

compareCost( CoOp& unit1, CoOp* unit2 )

{

double maint1, maint2;



maint1 = unit1.costPerShare * unit1.shares;

maint2 = unit2->costPerShare * unit2->shares;

// ...

}
Както unit1.costPerShare, така и unit2->costPerShare



се отнасят за статичния член CoOP::costPerShare.

Понеже съществува само едно копие на статичните член

данни на класа, те са достъпни и директно. Това изглежда така:

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


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

208.
if ( CoOp::costPerShare < 100.00 )
Операторът за обхват на класа ("CoOp::") трябва да бъде

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

а не глобален обхват за програмата.

Следващата дефиниция на приятелската функция

compareCost е еквивалентна на представената току що:
compareCost( CoOp& until1, CoOp *unit2 );

{

double maint1, maint2;



maint1 = CoOp::costPerShare * unit1.shares;

maint2 = CoOp::costPerShare * unit2.shares;

// ...

}
Двете член функции за достъп raiseCost() и getCost()



работят само със статичния член на класа costPerShare.

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

определен обект на класа. Въпросът е за кой обект? Обект тук е

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

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

разделен между всички обекти на класа. Това може да доведе до

неясноти в кода на програмата.

Поради това член функции, които работят само със

статични членове на класа могат също да бъдат декларирани като

статични. Това може да бъде направено така:


class CoOp

{

friend compareCost( CoOp&, CoOp* );



public:

CoOp( int, char* );

inline double monthlyMaint();

static void raiseCost( double incr );

static double getCost() {return costPerShare;}

private:


static double costPerShare;

int shares;

char *owner;

};
void CoOp::raiseCost( double incr )

{

costPerShare += incr;



}
Една статична член функция не съдържа указателя this;

следователно произволно явно или неявно използуване на this ще

предизвика грешка по време на компилация. Опитът за достъп до

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

указателя this. Например, monthlyMaint() не може да бъде

декларирана като статична член функция, защото й е необходим

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

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

209.
достъп до член данните shares. Дефиницията на една статична

член функция е същата както тази на една нестатична член

функция.

Една статична член функция може да бъде извикана през

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

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

функция. С нея може да се работи и директно като се използува

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


compareCost( CoOp& unit1, CoOp * unit2 )

{

// equivalent calls of static member getCost()



double maint1, maint2;

if ( CoOp::getCost() == 0 )

return 0;
maint1 = unit1.getCost() * unit1.shares;

maint2 = unit2->getCost() * unit2->shares;

// ...

}
Един статичен член на клас е достъпен и може да бъде викан



директно дори ако няма декларирани никакви обекти на класа.
Уппражнение 5-11. Даден е класа Y, съдъжащ два

статични члена данни и две статични член функции:


class X

{

public:



X( int i ) { val = i };

getVal() { return val; }

private:

int val;


};
class Y

{

public:



Y( int i );

static getXval();

static getCallsXval();

private:


static X Xval;

static callsXval;

};
Инициализирайте Xval с 20 и callsXval с 0.

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


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

210.
Упражнение 5-12. Реализирайте двете статични член

фуннкции за достъп. CallsXval просто брои колко пъти е

извикана getXval().

5.7. Указател към член на клас


Указателите, и осбено указателите към функции,

предлагат един полезен начин за обобщаване на програмите.

Потребителите на класа Screen, например, желаят да напишат

функция "повторител", която да изпълнява някаква дефинирана от

тях операция n пъти. Една възможна реализация има вида:
Screen &repeat( char op, int times )

{

switch( op )



{

case DOWN: // ... invoke Screen::down()

break;

case UP: // ... invoke Sscreen::up()



break;

// ...


}

}
Въпреки, че тази функция работи, тя има няколко

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

много явно се осланя на член функциите на Screen. Винаги

когато бъде добавяна или отстранявана някаква член функция,

repeat() трябва да бъде изменяна. Вторият проблем е свързан с

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

пълният списък в repeat() е огромен и изглежда много сложен.

Една алтернативна и много по-обща реализация заменя op

с аргумент от тип указател към член функция на Screen.

repeat() не е необходимо повече да определя предвижданите

оператори. Целият оператор switch може да бъде отстранен.

Дефинирането и използуването на указатели към членове на

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

Типът член на клас
На указател към функция не може да бъде пресвоен адрес

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

на двете функции напълно си съответствуват. Например, нека pfi

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

аргументи и има тип за връщане int:
int (*pfi)();
Screen дефинира две функции за достъп, getHeigth() и

getWidth(), които също не получават аргументи и имат типове за

връщане int:

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


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

211.
inline Screen::getHeigth() { return heigth; }

inline Screen::getWidth() { return width; }


С цел илюстрация са дефинирани също две нечлен функции

HeigthIs() и WidthIs():


HeigthIs() { return HEIGTH; }

WidthIs() { return WIDTH; }


Присвояването на едната или другата функция на pfi е напълно

възможно,


pfi = HeigthIs;

pfi = WidthIs;


Присвояването на една от член функциите на класа Screen

getHeigth() или getWidth(), обаче, се явява нарушаване на

типовете и ще предизвика грешка по влеме на компилация:
// illegal assignment: type violation

pfi = Screen::getHeigth;


Защо се получава нарушаване на типовете? Всяка член функция

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

функциите - класа й. Един указател към член функция трябва да

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


1. Типът на данните и броя на формалните аргументи;

т.е. сигнатурата.


2. Типът на данните за връщане.
3. Типът на класа, чийто член е функцията.
Декларацията на указател към член функция изисква един

разширен синтаксис, при който се взема предвид и типа на

класа. Същото се отнася и за поддържане на указатели към член

данни на клас. Да разгледаме типа на члена на класа Screen

heigth. Пълният тип на Screen::heigth e "член short на класа

Screen". Следователно, пълният тип на указател към

Screen::heigth е "указател към члена short на класа Screen".

Указателят this е дефиниран така:


short Screen::
Една дефиниция на указател към член на класа Screen от тип

short изглежда така:


short Screen::*ps_Screen;
ps_Screen може да бъде инициализиран с адреса на heigth така:
short Screen::*ps_Screen = &Screen::heigth;
По подобен начин на него може да бъде присвоен адреса на

width:


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

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

212.
ps_Screen = &Screen::width;
ps_Screen може да бъде инициализиран както с width така и с

heigth тъй като и двете са член данни на класа Screen от типа

short. При опит да се вземе адреса на непубличен член на класа

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

се генерира грешка по време на компилация.
Упражнение 5-13. Какъв е типа на членовете screen и

cursor на класа Screen?


Упражнение 5-14. Дифинирайте, инициализирайте и дайте

стойност на един указател към членовете screen и cursor на

класа Screen.
Указател към член функция се дефинира като се определи

нейния тип за връщане, сигнатурата и класа. Например, указател

към членовете getHeigth() и getWidth() се дефинира така:
int (Screen::*)()
Т.е., това е указател към член функция на Screen, която няма

аргументи и типът й за връщане е int. Указател към член

функция може да бъде инициализиран и да му бъде присвоена

стойност по следния начин:


// all pointers to class member may be assigned 0

int (Screen::*pmf1)() = 0;

int (Screen::*pmf2)() = Screen::getHeigth;
pmf1 = pmf2;

pmf2 = Screen::getWidth;


Използуването на typedef може да направи синтаксиса на

дефинирането на указатели към функции по-лесен за четене.

Например, следният typedef дефинира Action като алтернативно

име на тип за


Screen& (Screen::*)()
т.е. за указател към член функция на Screen, която не получава

аргументи и връща псевдоним на обект от класа Screen.


typrdef Screen& (Screen::*Action)();
Action deFault = Screen:home;

Action next = Screen::forward;


Упражнение 5-15. Дефинирайтe typedef за всички

различни типове членове на Screen.

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

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

213.
Указатели към членове могат да бъдат декларирани като

аргументи на функции; може да бъде зададее и инициализатор на

аргумент по подразбиране. Например,
action( Screen&, Screen& (Screen::*)() );
action() е декларирана да получава два аргумента:
1. Псевдоним на обект от класа Screen.
2. Указател към член функция на Screen, която не

получава аргументи и връща псевдоним на обект от

класа Screen.
action() може да бъде извикана по един от следните три

начина:
Screen myScreen;

typedef Screen& (Screen::*Action)();

Action deFault = Screen::home;


extern Screen&

action( Screen&, Action = Screen::display );


ff();

{

action( Screen&, Action = Screen::display );



action( myScreen, deFault );

action( myScreen, Screen::bottom );

}
Упражнение 5-16. Ууказатели към членове също могат да

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

дефиницията на класа Screen така, че да съдържа указател към

член функция на Screen от типа на home() и bottom().


Упражнение 5-17. Изменете съществуващия конструктор на

Screen (или задайте нов конструктор) така, че да получава

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

упражнение. Задайте аргумент по подразбиране. Нека някоя

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

стойност на този член.

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

бъдат достъпни през един специфичен обект на класа. Това може

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

операторите за избор на член (".*" за обекти класове и

псевдоними и "->" за указатели към обекти класове). Например,

указатели към член фуннкции могат да бъдат извикани така:

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

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

214.
int (Screen::*pmfi() () = Screen::getHeight;

Screen& (Screen::*pmfS) (Screen&) = Screen::copy;


Screen myScreen, *BufScreen;

// direct invocation of member function

if (myScreen.getHigth() == BufScreen->getHigth())

BufScreen->copy( myScreen );


//equivalent invocation through pointers to members

if ((myScreen.*pmfi)() == (BufScreen->*pmfi)())

(BufScreen->*pmfS) (myScreen);

Обръщенията


(myScreen.*pmfi)()

(BufScreen->*pmfi)()


изискват скоби понеже приоритетът на оператора за обръщение

("()") е по-висок от приоритета на указателя към оператора за

избор на член. Без скобите
myScreen.*pmfi()
ще бъде интерпретирано като
myScreen.*(pmfi())
т.е. като извикване на функцията pmfi() и свързване на

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

на обект (".*").

По подобен начин са достъпни и указателите към член

данните:
typedef short Screen::*ps_Screen;

Screen myScreen, *tmpScreen = new Screen(10,10);


ff()

{

ps_Screen pH = &Screen::heigth;



ps_Screen pW = &Screen::width;
tmpScreen->*pH = myScreen.*pH;

tmpScreen->*pW = myScreen.*pW;


}
Понеже heigth и wigth са лични членове на класа Screen

инициализирането на pH и pW в ff() е възможно само ако ff() е

декларирана като приятел за класа. Иначе всеки опит на ff() да

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

грешка по време на компилация.

Ето реализацията на член функцията repeat(), която

разгледахме в началото на този раздел:

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


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

215.

typedef Screen& (Screen::*Action)();


Screen&

Screen::repeat( Action op, int times )

{

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



(this->*op)();

return *this;

}
Декларация, която желае да предложи аргумент по

подразбиране за repeat() би могла да изглежда така:


class Screen

{

public:



Screen &

repeat( Action=Screen::forward, int=1 );

// ...

}
Обръщението към repeat() може да изглежда така:


Screen myScreen;

myScreen.repeat(); // repeat( Screen::forward, 1 );

myScreen.repeat( Screen::down, 20 );
Може също да бъде дефинирана и таблица от указатели

към членове на клас. В следващия пример Menu е таблица от

указатели към тези член функции на класа Screen, които

осигуряват движението на курсора. Освен това е дафиниран един

изброим тип CursorMovements, свързан с Menu.
Action Menu[] =

{

Screen::home,



Screen::forward;

Screem::back,

Screen::up,

Screen::down,

Screen::bottom

};
enum CursorMovements

{ HOME, FORWARD, BACK, UP, DOWN, BOTTOM };
Ще предложим един презаредим представител на move(),

който получава аргумент CursorMovements. Ето реализацията:

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

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

216.
Screen& Screen::move( CursorMovements cm )

{

(this->Menu[ cm ])();



return *this;

}
Този представител на move() може да се използува в

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

движението на курсора от меню, показано на екрана.


Упражнение 5-18. Дефинирайте презаредим представител

на repeat(), който получава аргумент CursorMovements.

Указатели към статични членове на клас
Статичните членове на даден клас се оказват вън от

синтаксиса на указателите към членовете на класа. Статичните

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

Декларацията на указател към статичен член на клас изглежда

така както тази на указатеел към некласов член. Съотнасянето

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

класа CoOp отново:
class CoOp

{

friend compareCost( CoOp&, CoOp* );



public:

CoOp( int, char* );

inline double monthlyMaint();

static void raiseCost( double incr );

static double getCost() { return costPerShare;}

private:


static double costPerShare;

int shares;

char *owner;

};
void CoOp::raiseCost(double incr)

{

costPerShare += incr;



}
Типът на &costPerShare е double*; той не е double CoOp::*.

Дефиницията на указател към costPerShare изглежда така:


// not double CoOp::*pd

double *pd = &CoOp::costPerShare;


Той е съотнесен по същия начин, по който се съотнасят и

обикновените указатели. Не изисква да бъде свързан с обект на

класа CoOp. Например,

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


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

217.
CoOp unit;

double maint = *pd * unit.shares;


По подобие на това типът на getCost() e double (*)();

а не е double(CoOp::*)(). Дефиницията на указател и

индиректното обръщение към getCost() се обработват по същия

начин както при некласовите указатели:


// not double (CoOp::*pf)()

double (*pf)() = CoOp::getCost;

double maint = pf()*unit.shares;

5.8. Обхват на клас


Всеки клас допуска както глобален така и локален

обхват. Въпреки, че дефиницията на един клас може да бъде

вложена в дефиницията на друг, двата класа се разглеждат като

притежаващи един обхват. Вложеният клас не се счита за член на

външния клас и няма никакви специални привилегии за достъп до

непубличните членове на този клас. Например, в раздел 4.2

(стр. 145) се дефинира IntList и личния клас IntItem. Ето една

еквивалентна дефиниция, при която дефиницията на IntItem е

вложена в дефиницията на IntList:
class IntList

{

class IntItem



{

friend class IntList;

private:

IntItem(int v=0) { val = v; next = 0; }

IntItem *next;

int val;


};

public:


IntList(int val) { list = new IntItem( val );}

IntList() { list = 0 }

// ...

private:


IntItem *atEnd();

IntItem *list;

};
IntItem не е нито член, нито личен член на Intlist. list, един

обект на класа IntItem е личен член на IntList. Както IntItem,

така и IntList имат файлов обхват. IntItem е лексикално вложен

в IntList за да показва на читателя, че се използува само от

IntList. За спецификации typedef, които се намират в

дефиницията на класа също се счита, че имат обхвата на външния

клас. Например,

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


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

218.
class Foo

{

typedef int Bar;



private:

Bar val; // ok

};
Bar val; // ok: the typedef is visible
Всеки клас поддържа свой собствен обхват. За имената

на членовете на класа се казва, че са локални за обхвата на

класа си. Ако името на променлива с файлов обхват се използува

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

Например,
int height;
class FooBar

{

public:



// FooBar::height <== 0

FooBar() { height = 0 }

private:

short height;

};
Въпреки, че height не се декларира в дефиницията на

Screen, тя е видима в цялото тяло на класа. До скритата

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

оператора за обхват. Например,


int height;
class FooBar;

public:


FooBar() { height = ::height; }

private:


short height;

};
Обратното, една променлива, дефинирана във функция, не

е видима до откриването на декларацията й. В следващия пример

localFunc() работи с два различни представителя на height.

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

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

219.
int height = 66;
localFunc()

{

int hi = height; // ::height



int height = 24; // hides ::height

hi = heigth; // hi <== 24

}
Следващият пример е още по-объркващ. За кой

представител на height мислите, че става дума?


int height = 66;
bdPrac()

{

int height = ::height;



}
Компилаторът вероятно няма да обърка двата

представителя на порменливата; читателят на програмата обаче

би могъл. Едно предпочитано в този случай решение е да се

преименува локалният представител до нещо, различно от heigth.

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

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

този на нечлен функциите. Ако едно име на член на клас се

използува в локалния обхват на дадена член функция, името на

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

представител. Например,


Screen::barPractice()

{

int hi = height; // Screen::heigth



int heigth = height; // heigth local instance

}
Достъп до скрития член на класа може да има през

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

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


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

220.
Screen::bdPrac()

{

int height = Screen::height;



}
Достъп до скрита глобална променлива е възможен и чрез

оператора за обхват. Например,


Screen::bdPractice()

{

int height =



(Screen::height > ::height)

? ::height : Screen::heeight;

}

Съглашения относно обхвата


Когато един идентификатор се явява в член функция на

някой клас, алгоретъмът, който свързва идентификатора е

следният:
1. Блокът, съдържащ даден идентификатор се претърсва

за декларация на идентификатора. Ако такава бъде

намерена идентификаторът се свързва; иначе се търси

в по-външен обхват.

2. Ако блокът, в който се явява идентификатора, е

локален блок, вложен в член функция, тогава

по-външен е блока на член функцията. Търси се

в обхвата на функцията. Ако се намери декларация,

идентификаторът се свързва; иначе се търси

по-външен обхват.


3. Ако блокът, в който се явява идентификатора е също

член функция, по-външният обхват е този на класа.

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

както и иднтификатора, то идентификатора се свързва

така, че да се отнася за този член; иначе се търси

по-външен обхват.


4. За свързването на идентификатор в член функция на

даден клас като по-външен обхват се приема

файловия, ако класът не е произлязъл от друг клас.

Ако бъде намерена декларация, идентификаторът се

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

недекларирана променлива.


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

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

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

дефинираме едно множество от оператори.

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

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

221.
extern f( int ), ff(), f3();

int i = 1024;


class Exsample

{

publlic:



f(); // hides ::f( int )

Example( int ii = 0 ){ i = ii; }

private:

int i; // hides ::i

int ff; // hides ::ff()

}
Един идентификато е скрит когато името му се използува във

вътрешен обхват дори когато типът на локалният представител е

различен. Глобалните идентификатори i, ff() и F(int) са скрити

в обхвата на класа Example. Във Example::f() обръщението към

тези идентификатори трябва да бъде предшествано от файловия

оператор за обхват.
#include "Example.h"
Example::f()

{

int j;


// error: file scope f() is hidden

// Exsample::f() takes no argument

j = f( 1 );
// ok: explicit refernce to ::f( int )

// i is resolved to Example::i;

j = ::f( i );
// ok: explicit reference is unnecessary

// file scope f3() is visible within Example

return( f3() );

}
Example::f() може също да дефинира локални

представители на i и f. Тези локални представители ще скрият

представителите на членовете на класа. За да работите с

членовете на класа трябва да използувате оператора за обхват

на клас.


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

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

222.
#include "Example.h"
Example::f()

{

// hides Example::i



int i = ff ? Example::i : ( ::ff() ? ::i : 0);
// error: Example::f3() undefined

return( Example::f3();

}

Локални класове


Един клас може да бъде дефиниран в локален за даден

файл обхват. Името на класа е видимо само в границите на

локалния му обхват. Например,
int doString( char *s)

{

// local class visible only within doString()



class String {...};

String str( s );

};
// error: String not visible

String str( "gravity" );


Член функциите на един лакален клас трябва да бъдат

дефинирани в тялото на класа. Например, недопустимо e следното

- една функция да бъде вложена в друга функция.

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


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

223.
int doString( char *s )

{

class String



{

public:


String& operator=(char*);

};
Striing::operator=(char* s) {} // illegal

}
Не може дадена член функция да бъде дефинирана вън от тялото

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

не е видимо. Например,
int stirng1(char *s) { class String { ... }; }

int string2(char *s) { class String { ... }; }


// error: no Stirng class visible

String::operator=(char* s) {}


На практика това налага ограничения върху размера и сложността

на член функциите на един локално дефиниран клас. Освен това

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

Въпреки, че локалният клас е в обхвата на функцията,

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

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

Например, bufSize в String се свързва с представителя,

дефиниран с файлов обхват, докато bufSize в func() се свързва

с локалния представител.
const int bufSize = 1024;

void func()

{

const int bufSize = 512;



char *ps = new char[bufSize]; // 512
class String

{

public:



String()

{

str = new char[ bufSize ]; // 1024 !



}

// ...
Дефинирането на един локален клас позволява на

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

функцията (или блока), в който е дефиниран класа. Изобщо, един

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

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

дефиниран.

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


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

224.
5.9. Обединение: Клас, който пести памет
Обединението е специален представител на клас.

Количеството памет, отделено за едно обединение, е

количеството, необходимо за най-големия му член данни. Всеки

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

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

Например, лексичният анализатор на компилатора разделя

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

Операторът,


int i = 0;
се преобразува в последователност от пет единици:
1. Ключовата дума за тип int.

2. Идентификаторът i.

3. Операторът =.

4. Цялата константа 0.

5. Разделителят ;.
Тези единиции се преглеждат от лексичния анализитор по време

на синтактичния разбор. Първата стъпка на синтактичния

анализатор е да определи последователността от единици.

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

аналазатор да може да разпознава потока от единици като

декларации - например, като следната последователност:


Type ID Assign Constant Semicolon
След като синтактичният анализатор вече е разпознал общата

последователност от единици той се нуждае от определена

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

знае, че
Type <==> int

ID <==> i

Constant <==> 0


Той не се нуждае от друга допълнителна информация относно

Assign и Semicolon.

Представянето на една единица, следователно, изисква

два члена token и value. token ще съдържа един уникален номер,

който се присвоява на всички възможни единици. Например,

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

и запетая чрез 72. value ще съдържа някаква информация за

представителя на единицата. Например, за ID, value ще съдържа

низът i; за Type - кодовото представяне на типа int.

Представянето на value е проблематично. Въпреки, че

value трябва да съдържа само една стойност за всяка дадена

единица, тази стойност може да има различен тип данни. Едно

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

клас. Компилаторът може да декларира value от тип TokenValue

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

типове данни на value.

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

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

225.
Такова представяне решава проблема. value, обаче, може

да бъде само от един от възможните типове данни за всяка

конкретна единица. TokenValue, обаче, ще отдели необходимата

памет за всички възможни типове данни. По-добре би било,

обаче, TokenValue да поддържа паметта, необходима само за един

от възможните типове данни, а не за всичките заедно.

Обединението позволява това. Ето дефиницията на обединението

TokenValue:


union TokenValue

{

char cval;



char *sval;

int ival;

double dval;

};
Тъй като най-много памет трябва да бъде отделена за типа данни

double, размерът на TokenValue е същия като този на double. По

подразбиране членовете на обединението са публични. Едно

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

обекти на клас, който дефинира конструктор или деструктор. Ето

един пример за това как TokenValue може да бъде използуван:
class Token

{

public:



int tok;

TokenValue val;

}
Един обект от тип Token може да бъде използуван по следния

начин:
lex()

{

Token currToken;



char *curString;

int curIval;


// ...

case ID: // identifier

curToken.tok = ID;

curToken.val.sval = curString;

break;
cose ICON: // integer constant

curToken.tok = ICON;

curToken.val.ival = curIval;

break;


// ...etc.

};
Опастността при използуване на обединение се състои в

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

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

226.
това, че е възможно случайно възстановяване на текущата

стойност на обединението чрез неподходящ член данни. Например,

ако при последното присвояване на стойност на обединението е

използувана цяла стойност ival, програмистът не би желал да

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

sval. Ако това бъде направено вероятно ще предизвика пограмна

грешка.

За предотвратяване на този тип грешки, оббикновено се



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

на стойността, текущо съхранена в обединението. Тази

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

За това служи члено tok на Token. Например,


char * idVal;
if ( curToken.tok == ID )

idVal = curToken.val.sval;


Съществува една добра традиция, когато се обработва

обект от тип обединение като част от даден клас да се създаде

набор от функции за достъп до всеки елемент на обединението.

Например,


char *Token::getSting()

{

if ( tok == ID )



idVal = curToken.val.sval;

error( ... );

}
Името на обединението е опционно. Няма никаква причина

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

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

в програмата има един глобален идентификатор по-малко и

съответно намаляват възможностите за възникване на колизии.

Например, следната дефиниция на Token е еквивалентна на

предходната дефиниция. Единствената разлика е, че обединението

няма име:


class Token

{

public:



int tok;

union


{

char cval;

char *sval;

int ival;

double dval;

} val;


}
Съществува един специален представител на обединение,

наречен анонимно обединение. Анонимното обединение е

обединение без име и не е следвано от дефиниция на обект.

Например, ето една дефиниция на класа Token, която съдържа

анонимно обединение:

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


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

227.

class Token



{

public:


int tok;

// anonimous union

union

{

char cval;



char *sval;

int ival;

double dval;

};

};


Член данните на един анонимен клас са достъпни

директно. Например, ето фрагмента lex(), написан така, че да

използува дефиницията на Token, която съдържа анонимно

обединение:


lex()

{

Token curToken;



char *curString;

int curIval;


// ... figure out what the token is

// ... now set curToken

case ID:

curToken.tok = ID;

curToken.sval = curString;

break;


case ICON: // integer constant

curToken.tok = ICON;

curToken.ival = curIval;

break;


// ... etc.

}
Всяко анонимно обединение отстранява едно ниво за

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

влизат във външния обхват. Едно анонимно обединение не може да

има лични или защитени членове. Анонимно обединение,

дефинирано с файлов обхват трябва да бъде декларирано като

static.

5.10. Разредно поле: член, който пести памет


Един спициален член данни на клас, наречен разредно

или битово поле, се състои от определен брой битове. Той може

да бъде със или без знак. Например,

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


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

228.
class File

{

// ...



unsigned modified : 1; // bit field

};
След идентификатора на едно битово поле се записва

двуеточие и константен израз, който определя брой на битовете.

modified, например, е разредно поле, което се състои от един

бит.

Разредни полета, дефинирани в последователен ред в



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

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

оситурява компактност на съхраняването. Например, в следната

декларация, петте разредни полета са записани на място,

отделно за едно unsigned int, което е свързано първо със

съвкупността от битови полета.

typedef unsigned int Bit;
class File

{

public:



// ...

private:


Bit mode: 2;

Bit modified: 1;

Bit prot_owner: 3;

Bit prot_group: 3;

Bit prot_word: 3;

// ...


};
Едно разредно поле е достъпно по същия начин, както и

останалите член данни на даден клас. Например,


File::write()

{

modified = 1;



// ...

}
File::close()

{

if ( modified )



// ... save contents

}
Ето един прост пример за това как може да бъде

използувано битово поле по-голямо от един бит (Раздел 2.8

(стр. 74) операциите за работа с битове, използувани в

примера):

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


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

229.
enum { READ = 01, WRITE = 02 }; // File modes
main()

{

File myFile;


myFile.mode |= READ;

if ( myFile.mode & READ )

cout << "\nmyFile.mode is set to READ";

}
Обикновено се дефинира едно множество от член функции inline

за да се проверява стойността на члена разредно поле.

Например, File може да дефинира isRead() и isWrite().


inline File::isRead() { return mode & READ; }

inline File::isWrite() { return mode & WRITE; }


if ( myFile.isRead() ) /* ... */
Операторът адрес-на ("&") не може да се прилага към

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

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

полета могат да бъдат декларирани като static.

5.11. Аргумент клас и многоточие
Обект на клас, който дефинира конструктор или

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

изпращан като аргумент на функция без да определи подходяща

декларация на аргумента. Например,


extrn foo( int, ... );
class Screen

{

public:



Screen( const Screen& );

// ...


};
void bar( int ival, Screen scrObj )

{

// error: no Screen argument spesified



foo( ival, scrObj );

}
Възможно е, обаче, да бъде изпратен указател към scrObj за

foo().

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


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

230.

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


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

231.


Каталог: 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   ...   8   9   10   11   12   13   14   15   ...   19




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

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