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



страница15/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   11   12   13   14   15   16   17   18   19

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

("!"), например, не може да бъде дефиниран като бинарен

опрератор за неравенство на два обекта от класа String.

Неправилна е следната реализация:


// illegal: ! is a unary operator

operator!( String st1, String st2 )

{

return( strcmp(st1.str, st2.str ) != 0 );



}
Четири предварително дефинирани оператора ("+", "-",

"*" и "&") притежават както унарни, така и бинарни

представители. Могат да бъдат дефинирани оператори с всяка от

двете арности.

Може да има само по един представител на унарното

увеличаване или намаляване с единица ("++", "--"). При

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

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

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

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

слепване на два обекта на класа String,
String st1( "cobble" );

String st2( "stone" );

String st3 = st1 + st2;
може да бъде дефиниран оператор за събиране или като нечлен

приятел на Stirng:


class String

{

friend String& operator+( String&, String& );



// ...

};
или като негова член функция:


class String

{

public:



String& operator+( String& );

// ...


};
Дефинирането и на двата представителя вероятно ше предизвика

двусмислие:

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

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

257.
class String

{

friend String& operator+( String&, String& );



public:

String& operator+( String& );

// ...

};
String a( "hobby" ), b( "horse" );



String c = a + b; // error: anbiguous
Член функцията на един клас винаги дефинира аргументи

операнди с един по-малко от една функция приятел. Това се

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

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


st1 + st2
може да бъде дефиниран като член функция със следната форма:
st1.operator+( st2 )
или като нечлен функция с формата:
operator+( st1, st2 )
Не може да се зададат и двете дефиниции, понеже компилаторът

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

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

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

присвояване ("="), индексния оператор ("[]"), опеартора за

обръщение ("()") и оператора указател за избор на член ("->").

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

на тази глава.

Една член функция на клас изисква обект от класа си

за ляв операнд. Операция, която не изсква за ляв операнд обект

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

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

да бъде декларирана като приятелска. Например,
class String

{

friend ostream&



operator<<( ostream& os, String& s )

{ return ( os << s.str ); }

// ...

};
Упражнение 6-11. Декларирайте прототипа на унарната



оператор фуннкция operator+() - първо като приятел нечлен и

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

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

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

258.
Упражнение 6-12. Логическият оператор NOT връща 1 ако

обектът на String е празен; иначе връща 0. Реализирайте го

като оператор на String.
Упражнение 6-13. Бинарната оператор функция

operator+() слепва два обекта на String. Реализирайте този

оператор като нечлен.
Упражнение 6-14. Реализирайте представител на

operator+() като член функция на String.

Оператор []
Потребителите на нашия клас String желаят да имат

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

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

използуване на обектите на класа String:


String sentence( "Ash on an old man's sleeve." );

String tempBuf( sentence.getLen() );


for ( int i = 0; i < sentence.getLen(); ++i )

tempBuf[ i ] = sentence[ i ];

String::getLen() е една проста функция за достъп, която връща

дължината на обекта на String:


inline String::getLen()

{

return len;



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

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

постигнато това връщаната от него стойност трябва да бъде

стойност за запис. Тази стойност може да баде дефинирана от

тип псевдоним:
inline char& String::operator[]( int elem )

{

checkBounds( elem );



return str[ elem ];

}
Връщаната от индексния оператор стойност е стойността за запис

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

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

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

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

259.
String st( "mauve" );

st[0] = 'M';


присвояване на стойността на константа на нуловия елемент на

st.str.


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

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

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

Реализацията изглежда така:


// pull in exit(int) prototype

#include


void String::checkBounds( int elem )

{ // check array bounds


if ( elem < 0 || elem >= len )

{

cerr << "\nString Array Out of Bounds! index: "



<< elem << " string length (0-"

<< len-1 << ")\n";

exit( -1 );

}

}

Оператор ()


Един повторител позволява на потребителя да преглежда

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

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

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

как може да бъде използуван един повторител за класа String:
String inBuf;
// read in a String object

while ( cin >> inBuf )

{

char ch;


// iterate over the elements of inBuf

while ( ch = inBuf() )

// ... do something with ch

}
Този подраздел ще разгледа как този програмен фрагмент

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

функции: нечлен функцията >>() и член функцията на String

operator().

Операторът за вход е презареден за четене от обект на

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

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


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

260.
istream& operator>>( istream& is, String& s )

{

char inBuf[ STRING_SIZE ];


is >> inBuf;

s = inBuf; // String::operator=( char * )

return is;

}
Входният оператор функция на String трябва да присвоява char*

на обект на String. Това може да бъде обработено чрез

представител на String::operator=(char*).


String& String::operator=( const char *s )

{

len = strlen( s );



delete str;

str = new char[ len + 1 ];

strcpy( str, s );

return *this;

}
Оператор функцията за обръщение () предлага един лесен

начин за дефиниране на повторител за клас, който връща

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

прегледан целия масив от елементи, повторителят връща 0. Това

позволява използуването на конструкции като:
while ( ch = inBuf() ) // ... code
За реализирането на повторител на String е необходим един

допълнителен член данни - index. index ще адресира следващия

елемент, който трябва да бъде върнат. Всеки конструктор на

String трябва да инициализира index с 0.


char String::operator() ()

{

// provide for an iterator operator



if ( index < len )

return str[ index++ ];


// still here? completed iteration

return ( index = 0 );

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

на String, използва повторителя, индексния оператор и

потоковите оператори.

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


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

261.
#include "String.h"
const LINESIZE = 40;

enum {PERIOD='.',COMMA=',',SEMI=';',COLON=':'};


main()

{

String inBuf( STRING_SIZE );



int lineSize = 0;
// operator>>( istream&, String& )

while ( cin >> inBuf )

{

char ch;


int i = 0;
// String::operator() ()

while ( ch = inBuf() )

{

switch(ch)



{

case PERIOD:

case COMMA:

case SEMI:

case COLON:

// String::operator[](int);

inBuf[ i ] = '\0';

break;


}

++i; ++lineSize;

}

if ( lineSize >= LINESIZE )



{

cout << "\n";

lineSize = 0;

}

cout << inBuf << " ";



}

cout << "\n";

}
Програмата има следните входни данни:
We were her pride of ten; she named us: benjamin,

phoenix, the prodigal, and perspicacious, pacific

Suzanne. Benjamin, hush now. Be still, child.

People are never just.


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

получава следния изход:

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

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

262.
We were her pride of ten she named us

benjamin phoenix the prodigal and perspicacious

pacific Suzanne Benjamin hush now Be still child

People are never just

Оператори new и delete
По подразбиране отделянето на свободна памет за обекти

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

представител на оператора new (разгледан в Раздел 4.1 (стр.

135) ). Например, следната пограма чете последователност от

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

наречен StringList.


#include "String.h"

#include "StringList.h"


// maintain a pointer table indexed by length

const maxLen = 25;

StringList *stbl[ maxLen ];
main()

{ // read in and sort strings by length

const inBuf = 512;

char st[ inBuf ]

StringList *p;
while ( cin >> st )

{

p = new StringList( st );



int sz = p->getLen();

if ( sz >= maxLen )

// issue error message

continue;

p->next = stbl[ sz ];

stbl[ sz ] = p;

}
for ( int i = maxLen - 1; i > 0; --i )

{

StringList *tp;



p = stbl[ i ];

while ( p != 0 )

{

cout << *p << "\n";



tp = p;

p = p->next;

delete tp;

}

}



}

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


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

263.
StringList дефинира два член данни, един обект на

класа String, наречен entry и един указател към String,

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

char*. Този аргумент се изпраща на конструктора на String за

инициализиране на entry.
StringList::StringList( char *s )

: entry( s ), next( 0 )

{ }
getLen() е фуннкция за достъп. Тя връща дължината на

entry.
StringList::getLen()

{

return entry.getLen();



}
Извиква се функцията за достъп на String() getLen()

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

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

представител на StringList да напише следното:


StringList::getLen()

{

// illegal: private member



retrun entry.len

}
Операторът за изход е презареден да възприема

псевдоним на StringList:
ostream&

operator << ostream& os, StringLien& s )

{

retrun ( os << s.entry );



}
Входът на програмата е темата на този подраздел:
A class may provide new and delete

operator functions


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

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

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

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

264.
functions

operator


provide

delete


class

and


new

may


A
След като вече сме сигурни, че пограмата работи

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

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

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

прочитане на дума. Ние бихме могли да променим това -

например, да отделяме памет за всеки 24 думи - така ще се

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

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

чрез задаване на оператори new и delete членове на класа. Ако

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

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

промени.


Стратегията за управление на паметта, която ще

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

на памет за няколко обекта на StringList. Тези обекти ще бъдат

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

freeStore и stringChunk:
class StringList

{

private:



enum { stringChunk = 24; }

static StringList * freeStore;

// ...

};
stringChunk показва броя обекти на StringList, за които се



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

списък от налични обекти на StringList.

Един оператор new представител на член на клас трябва

да дефинира тип на връщане void* и да получава първи аргумент

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

файл stddef.h. Този аргумент автоматично се инициализира от

компилатора с размера на типа на класа в байтове. Могат също

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

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

Раздел 4.3 (стр. 164) разглежда презареждането на оператора

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

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

Ако такъв бъде намерен, той се избира; иначе се прилага

предварително дефинирания глобален представител. Добавянето

или отстраняването на представител new на класа не изисква

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

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

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

265.
Ето един пример представител на оператор new член на

StringList. Той проверява дали има наличен обект от freeStore.

Ако такъв има, той се подава от члена представител. Иначе, се

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

stringChunk на брой обекти. Ето реализацията:
#include
StringList *StringList::freeStore = 0;

void *StringList::operator new( size_t size )

{

register StringList *p;


// if the free store is exhausted

// grab a new chunk of memory

if ( !freeStore )

{

long sz = StringChunk * size;



freeStore = p =

// the global new operator

(StringList *)new char[ sz ];
// initialize the StringList freeStore

for ( ; p != &freeStore[ StringChunk-1 ];

p->next = p+1, p++ );

p->next = 0;

}

p = freeStore;



freeStore = freeStore->next;

return p;

}
Член операторът delete възстановява обект на

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

реализацията му:
void StringList::operator delete( void *p, size_t )

{ // restore p to freeStore

((StringList*)p)->next = freestore;

freeStore = (StringList *)p;

}
Операторът delete трябва да задава първи аргумент от тип

void*. Като втори аргумент може да бъде зададен аргумент от

системния тип typrdef size_t (не забравяйте да включите

stddef.h). Ако е зададен, той неявно се инициализира с размера

в байтове на обекта, адресиран от първия аргумент. Операторът

delete трябва да дефинира тип за връщане void.

Представителят на клас на оператора new се извиква

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

масиви от обекти. Например,

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


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

266.
StringList *p = new StringList;
извиква представителя на new на StringList, докато
StringList *pia = new StringList[10];
извиква предварително дефинирания представител, който

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

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

такова като


StringList s;
също не извиква представителя на оператора new.

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

оператор new чрез оператора за обхват. Например,
StringList *ps = ::new StringList;
извиква оператора new по подразбиране. Съответно,
::delete ps;
извиква представителя на оператора delete по подразбиране.

Оператор функциите new и delete са статични членове на

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

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

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

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

класа си. (Виж раздел 5.6 (стр. 208) относно статичните член

функции). Тези оператори са направени статични член функции

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

(new) или след разрушаването му (delete).

X::Operator=(const X&)
Присвояването на един обект на клас на друг обек от

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

нестатичните член данни; механизмът е същия като този на

почленовата инициализация, описан в раздел 6.2 (стр. 247) на

тази глава.

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

присвояване на класа със следната форма:
X& X::operator=( const X& );
за да обработва по подразбиране почленовото присвояване на

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

String:

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


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

267.
String article( "the" );

String common( "For example" );


и оператора за присвояване
common = srticle;
който се обработва чрез явния почленов оператор за

присвояване:


String& String::operator=( const String& s )

{

len = s.len;



str = s.str;

index = s.index;

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

присвояването.


1. Както и при почленната инициализация article и

common сега адресират една и съща област от

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

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

област.
2. Паметта, отделена за да съдържа For example никога

не се освобождава. Тя се загубва по време на

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

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

(стр. 260) дефинирахме член данни index на класа

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

символния масив на String. Стойността на index след

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

назначение от класа String - не е така, обаче, за

обекта източник. Почленното копиране по

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

String.
Проектантът на класа може да разреши тези проблеми

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

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

дефинирани по следния начин:
String& String::opperator=( const String& s )

{

index = 0;



len = s.len;

delete str;


str = new char[ len + 1 ];

strcpy( str, s.str );

return *this;

}
StringList, дефиниран в предишния раздел, съдържа член

на класа String, наречен entry. StringList не дефинира

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


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

268.
оператор за почленно присвояване. Когато един обект на

StringList се присвоява на друг обект от класа StringList,

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

обработи почленното копиране на entry. Например,


#include "StringList.h"
main()

{

StringList sl1( "horse" );



StringLIst sl2( "carriage" );
// sl1.next = sl2.next

// sl1.entry.String::operator=(sl2.entry)

sl1 = sl2;

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

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

тялото на оператора явно присъствува присвояване на Stirng.

Например,
StringList&

StringList::operator=( const String& s )

{

// String::operator=(const String&)



entry = s.entry;

next = 0;

return *this;

}
Ако присвояването


entry = s.entry;
не е написано, няма да бъде извикан почленния оператор - entry

няма да бъде променен.

Често инициализацията и присвояването не се

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

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

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

са следните две дефиниции на клас:
class X

{

public:



X();

X( int );

X( const X& );

X& operator=( const X& );

// ...

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


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

269.
class Y

{

public:



Y();

private:


X x;

};
като следната проста реализация на конструктора на класа Y:


Y::Y() { x = 0; }
предизивиква явно извикване на два конструктора на X плюс

извикване на оператора за присвояване за X:


1. Конструктора по подразбиране
X::X()
се извиква преди конструктора на Y за да

инициализира обекта x член на клас.


Присвояването на стойност 0 на x не може да бъде

извършено директно понеже класът X не дефинира

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

от тип int. Присвояването се извършва на два етапа:


2. Извиква се конструктора
X::X( int );
за да преобразува цялото число 0 в обект от тип X.

(Използуването на конструктори за преобразуване на

типовете се разглежда в раздел 6.5 (стр. 286) по

нататък в тази глава).


3. Този новосъздаден обект на класа X се присвоява на

X чрез извикването на


X::operator=( const X& )
Второто и третото извикване са ненужни и няма да бъдат

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

x:
Y::Y() : x( 0 ) {}
Сега ще бъде извикан само
X::X( int )
при всяко извикване на конструктора на класа Y.

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


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

270.
Оператор ->
Операторът за избор на член ("->") се обработва като

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

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

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

обект на клас или обект на клас, за който операторът -> e

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

който връща String*.
#include "String.h"
class Xample

{

public:



String *operator->();

// ...


private:

String *ps;

// ...

};
Eто една схематична реализация на оператора. Той връща



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

сочи.
String* Xample::operator->()

{

if ( ps == 0 )



// ... initialize ps

// ... process ps

return ps;

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

обект или за псевдоним на обект на класа Xample. Например,
void ff( Xample x, Xample &xr, Xample *xp )

{

int i;


// invoke String::getLen()

i = x->getLen(); // ok: x.ps->getLen()

i = xr->getLen(); // ok: xr.ps->getLen()

i = xp->getLen(); // error: no Xample::getLen()


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

чрез указател към Xample понеже компилаторът не може да

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

представител.

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

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

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

оператора за присвояване ("=") и адресния оператор ("&").

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

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

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

класа.


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

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

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

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

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

Всеки оператор се свързва с някакво значение от

предварително дефинираното му използуване. Бинарният +,

например, се отнася за събирането. Съпоставянето на + на някоя

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

кратък запис. Например, конкатенацията на обекти на String е

едно подходящо разширение на +.

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

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

операторите. isEmpty() става за логически оператор NOT,

operator!(); isEqual става за оператор за равенство,

operator==(); copy() става за оператор за присвояване,

operator=(); и т.н.

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

дефинирани явно за един клас. Операторите на String, например,

поддържат конкатенация и почленово копиране:


String s1( "C" );

String s2( "++" );


s1 = s1 + s2; // s1 <== "C++"
Тези оператори, обаче, не поддържат еквивалентния

оператор за присвояване:


s1 += s2;
Този оператор също трябва явно да бъде дефиниран и да предлага

еквивалентна семантика.


Упражнение 6-15. Определете кои член функции на класа

Screen, дефиниран в Глава 5 могат да бъдат презаредени чрез

оператори.
Упражнение 6-16. Реализирайт е презаредимите оператор

функции, зададени от предишното упражнение.

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

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

272.
Упражнение 6-17. В раздел 4.2 (стр. 141) беше

дефиниран клас - списък от цели числа, IntList. Определете и

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

функции: isEmpty(), isEqual(), print(), addNode(), build(),

copy() и deleteNode().
Упражнение 6-18. Кои от тези член функции са кандидати

за презареждане? Кои от тях са кандидати за презаредими

оператори?
Упражнение 6-19. Реализирайте член функциите на

бинарното дърво, изброени в горния параграф.


Упражнение 6-20. INodes се генерират доста често при

работа с BinTree. Напищете нов начин за управление на паметта,

като използувате операторите new и delete.

6.4. Пример за клас BitVector


В Раздел 2.8 (стр. 76) бяха показани вектори от битове

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

предишните раздели беше използуван класа String за въвеждане

на концепциите за презареждане на оператори. В този раздел ще

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

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

презаредими оператор функции. Ще наречем нашия клас BitVector.
typedef unisgned int BitVecType;

typedef BitVecType *BitVec;


class BitVector

{

private:



BitVec bv;

unsigned short size;

short wordWidth;

};
size съдържа броя битове, които обектът на класа

BitVector представя. bv сочи фактическия вектор от битове,

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

цели числа без знак. wordWidth съдържа броя цели цисла без

знак, сочени от bv. Например, ако цялото число без знак се

записва в 32 бита BitVector инициализира членовете си със

следните стойности:

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

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

273.
// number of bits in bit vector

size = 16;


// number of unsigned ints to hold bit vector

wordWidth = 1;


Един BitVector от 107 бита, обаче, не може да бъде представен

с по-малко от четири цели числа без знак. Неговите членове се

инициализират със следните стойности:
size = 107;

wordWidth = 4;


И в двата случая bv се инициализира с масив от

елементи от тип BitVecType с размерност wordWidth. Например,


bv = new BitVecType[ wordWidth ];
Размерът в битове и байтове на типовете данни

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

дефинираме явни псевдоними за тези машинно зависими стойности

в текста на програмата си:


#ifdef vax

const BITSPERBYTE = 8;

const BYTESPERWORD = 4;

#endif
#ifdef sun

const BITSPERBYTE = 8;

const BYTESPERWORD = 2;

#endif
// The size of a machine integer

const WORDSIZE = BITSPERBYTE * BYTESPERWORD;


Потребителят трябва да има опция за избор на размер на

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

показва един размер. Най-логичната стойност по подразбиране за

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

знак, най-малката единица, която може да бъде сочена от bv.

Ето конструктора на нашия клас BitVector:

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

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

274.
enum { OFF, ON };
// default values: sz => WORDSIZE, init => OFF

BitVector::BitVector( int sz, int init )

{

size = sz;



wordWidth = (size + WORDSIZE - 1)/ WORDSIZE;

bv = new BitVecType[ wordWidth ];


// now initialize bv to either all 0's or 1's

if ( init != OFF ) init = ~0;


// assign 0 or -1 to each word of bv

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

*(bv + i) = init;

}
Протребителят трябва да може да задава ( set() ) и

отменя ( unset() ) стойност на отделни битове. Това са

бинарнии операции, които изискват обект на класа BitVector и

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

брой възможни оператори. Например, за да зададе стойност на

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

оператори:


BitVector bvec;
// possible operator for set()

bvec | 12;

bvec |= 12;

bvec + 12;

bvec += 12;
Задаването на стойност единица за един бит е по-подобна на

операцията събиране отколкото побитовата операция OR. Как да

направим избор, обаче, между операторите "+" и "+="? Трябва ли

да реализираме и двата? Нека да разгледаме какво фактически

значи операцията.

Като функция, която не е операто set() се извиква

така:
bvec.set(12);
bvec се изменя като в 12-я бит получава стойност 1. Това

означава, че левият операнд на израза запазва резултата от

израза. Това не е естественото поведение на оператора "+", но

отговаря на поведението на оператора "+=". Това е оператора,

който избираме. Ето и реализацията:

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


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

275.
void BitVector::operator+=( int pos )

{ // turn on bit at position pos


checkBounds( pos );
int index = getIndex( pos );

int offset = getOffset( pos );


// turn oon bit offset at word index

*(bv + index) |= (ON << offset);

}
getIndex() и getOffset() лични помощни функции.

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

бита. Например, бит 16 връща индекс 0; бит 33 връща индекс 1;

а бит 107 - 3. Ето реализацията й:


inline BitVector::getIndex( int pos )

{

// return word bit is positioned in



return( pos / WORDSIZE );

}
getOffset() връща позицията на бита в цялото число без

знак, което го съдържа. Например, бит 16 връща отместване 16;

бит 33 връща отместване 1; а бит 107 - 11. Ето реализацията на

функцията:
inline BitVector::getOffset( int pos )

{

// return position of bit in word



return( pos % WORDSIZE );

}
Реализацията на BitVector::operator-=(int) е същото,

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

стойност 0.


// turn off the bit

// in worg index at position offset

*(bv + index) &= (~(ON<Потребителят не трябва да разрешава излизане извън

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

да бъде откривано:
BitVectro bvec( 16 );

bvec += 18; // error: out of bounds


Ето една възможна реализация:

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


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

276.
void BitVctor::checkBounds( int index )

{

// make sure index is within bounds of BitVector



if ( index < 0 || index >= size )

{

cerr << "\nBitVector Index Out of Bounds: "



<< "<< " << index

<< ", size: " << size << " >>\n";

exit( -1 ); // stdlib.h

}

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



стойността на определени битове. isOn() и isOff() са бинарни

опреации с един ляв операнд - обект на класа BitVector и един

десен операнд - цяло число представящо определен бит. За

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

равенство ("==") и неравенство ("!=").

Например, за да провери дали бит 17 има стойност 1

потребителят би могъл да напише следното:
BitVector bvec;
if (bvec == 17 )

// ...
Ето реализацията:


BitVector::operator == ( int pos )

{

// true if bit at position pos is 1



checkBounds( pos );
int index = getIndex( pos );

int offset = getOffset( pos );


return( *(bv + index) & (ON << offset) );

}
BitVector::operator != ( int pos )

{ // return the negation of operator==()

return ( !(*this == pos ) );

}
Потребителят на BitVector трябва да може да показва

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

показването на обекти на BitVector с проказването на

вградените типове данни. Това може да бъде направено чрез

презареждане на оператора за изход ("<<") като типа за връщане

се дефинира като ostream&. Тогава потребителите на BitVector

ще могат да пишат следното:

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


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

277.
cout << "my BitVector: " << bv << "\n";
Ако битове 7, 12, 19, 27 и 34 имат стойност 1 извеждането на

bv ще изглежда така:


< 7 12 19 27 34 >
Оператор функцията може да бъде дефинирана по следния

начин:
ostream& operator<<(ostream& os, BitVector& bv )

{

int lineSize = 12;



os << "\n\t< ";
for ( int cnt = 0, i = 0; i < bv.size; ++i )

{ // BitVector::operator==(int)

if ( bv == i )

{

// worry about line format of output



int lineCnt = cnt++ % lineSize;

if ( lineCnt == 0 && cnt != 1 )

os << "\n\t ";

os << i << " ";

}

}
if ( cnt == 0 ) os << "(none) ";



os << ">\n";

return os;

}
Понякога даден оператор трябва да бъде преобразувана в

оператор функция. Ето един пример.

За да използува повторно обект на BitVector

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

Може да бъде реализирана функция reset() като унарна операция

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

чрез логическия оператор NOT ("!"):
BitVector& BitVector::operator!()

{ // reinitialize all elements to 0

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

*( bv + i ) = 0;

return *this;

}
Тази реализация на оператора NOT не съответствува на

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

употребявана от програмиста. Ето защо.

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

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

278.
- Според предварителната му дефиниция той връща

стойност истина ако операндът му има стойност 0. Той

не изменя операнда си.
- В реализацията си за BitVector той връща псевдоним

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

стойност 0.
Ето една много вероятна програмна грешка. В този

пример testResults() връща BitVector, в който всеки бит

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

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

на BitVector с предварително дефинираната му употреба:
if ( !( bvec = testResults() ))

cout << "All Tests passed!\n";

else

reroutFailures( bvec );



// ...
В нашата реализация reset() e останала като именувана член

функция.


Векторите от битове са общо използувани в

компилаторите оптимизатори за поддържане информация отнасяща

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

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

битове; те са основни за класа BitVector.

За представяне на тези операции се използуват

операторите & и |. За простота представената реализация

допуска, че двата обекта на BitVector са от един и същ

размер.
#include "BitVector.h"
BitVector BitVector::operator | (BitVector& b)

{ // simplifying assumption: both have same size


BitVector result ( size, OFF );

BitVec tmp = result.bv;


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

*(tmp + i) = *(bv + i) | *(b.bv + i);


return result;

}
Отбележете, че реализацията на оператора & е същата както и на

оператора |, само че изпълняваната битова операция е AND:
*(tmp + i) = *(bv + i) & *(b.bv + i);
Фиг. 6.2 представя една малка интерактивна програма,

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

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

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


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

279.
unsigned const char * ;
ще зададе стойност 1 на четири бита от обектa typeFlag на

BitVector. За упростяване на представянето на програмата се

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

иска да въвежда всеки атрибут, отделяйки го с точка и

запетая.

Когато се компилира и изпълни програмата от Фиг. 6.2

се получават следните резултати:
Type in a declaration -- end with ';'

preceded by white space. For exampleq try

'unsigned const char * ;'

Hit ctrl-d to exit program


unsigned const char * ;
flags set for declaration are:

unsigned


const

*
char


Фиг. 6.3 използува заглавния файл BitVector на класа

BitVector, реализиран в този раздел.


Упражнение 6-21. Променете програмата, описана във

фиг. 6.2 така че да обработва входни данни със следния формат:


const char * const ;
Упражнение 6-22. Добавете представители на X(const X&)

и operator=(const X&) за класа BitVector.


Упражнение 6-23. Дефинирайте оператор за равенство на

два обекта на BitVector.


Упражтение 6-24. Преобразувайте оператор функцията AND

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


Упражнение 6-25. Преобразувайте оператор функцията OR

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

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

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

280.
#include

#include "BitVector.h"


const int MAXBITS = 8;
enum { ERROR, CHAR, SHORT, INT, PTR,

REFERENCE, CONST, UNSIGNED };


static char *typeTbl[] = {

"OOPS, no type at slot 0",

"char", "short", "int",

"*", "&", "const", "unsigned" };


static char *msg =

"Type in a declaration -- end width ';'\

\npreceded by white space. For example, \

try\n\t'unsigned const char * ;' \

\nHit ctrl-d to exit program\n";
main()

{

BitVector typeFlags( MAXBITS );



char buf[ 1024 ];
cout << msg;

while ( cin >> buf )

{

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



if (strcmp( typeTbl[i], buf ) == 0)

{ // a keyword? BitVector::operator+=

typeFlags += i; break;

}
if ( buf[0] == ';' )

{ // end of entry

cout <<"\nflags set for \

declaration are:\n\t";

for ( i = MAXBITS-1; i > 0; i--)

// BitVector::operator==

if ( typeFlags == i )

cout << typeTbl[i]

<< "\n\t";

cout << "\n";

// reinitialize:

// BitVector::reset()

typeFlags.reset();

}

}



}
Фиг. 6.2 Програмен пример за използуване на BitVector

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


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

281.
#ifndef BITVECTOR_H

#define BITVECTOR_H


#ifdef vax

const BITSPERBYTE = 8;

const BYTESPERWORD = 4;

#endif
#ifdef sun

const BITSPERBYTE = 8;

const BYTESPERWORD = 2;

#endif
const WORDSIZE = BITSPERBYTE * BYTESPERWORD;

enum { OFF, ON };

typedef unsigned int BitVecType;

typedef BitVecType *BitVec;


#include

class BitVector

{

friend ostream&



operator<<( ostream&, BitVector& );

public:


BitVector( int = WORDSIZE, int = OFF );

~BitVector() { delete [wordWidth] bv; }

void operator+=( int pos ); // turn on pos

void operator-=( int pos ); // turn off pos

BitVector operator &( BitVector& );

BitVector operator |( BitVector& );

operator == ( int pos ); // pos is on

operator != ( int pos ); // pos is off

void reset(); // reinit to 0

private: // helping functions

void checkBounds( int );

inline getOffset( int );

inline getIndex( int );

private: // internal representation

short wordWidth;

short size;

BitVec bv;

};

#indif


Фиг. 6.3 BitVector.h

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


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

282.
Упражнение 6-26. Променете представителя на оператора

за изход на BitVector така че да сгъстява последователните

битове с еднаква стойност. Например, ако текущия изход е:
< 0, 1, 2, 3, 4, 5, 6, 7 >

< 0, 2, 3, 4, 7, 8, 9, 12 >
Реализирайте оператора за изход така че да групира

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

Например,
< 0 - 7 >

< 0, 2 - 4, 7 - 9, 12 >

6.5. Конвертори, дефинирани от потребителя


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

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

оператори и презаредими функции. Например, без аритметичните

конвертори следните шест операции събиране ще изискват

уникални реализации:
char ch; short sh; int ival;
// widout type conversion, each addition

// would require a unique operation

ch + ival; ival + ch;

ch + sh; sh + ch;

ival + sh; sh + ival;
Чрез аритметичните конвертори всеки опернд се преобразува към

типа int. Необходима е само една операция - за събиране на две

цели стойности. Тези преобразувания се обработват неявно от

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

В този раздел ще разгледаме как проектантът на един

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

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

необходимо. За илюстриране на нашето обсъждане ще реализираме

класа SmallInt.

Класът SmallInt може да поддържа стойности от един и

същ обхват като 8-битови unsigned char - т.е. 0 - 255.

Допълнителни възможности са, че се откриват грешки, свързани с

малки и големи числа, излизащи извън обхвата. Иначе, искаме

класът да се държи по същия начин както и unsigned char.

Например, ние бихме желали да добавяме и изваждаме

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

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

шест оператор функции на SmallInt:

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

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

283.
class SmallInt

{

friend operator+( SmallInt&, int );



friend operator-( SmallInt&, int );

friend operator-( int, SmallInt& );

friend operator+( int, SmallInt& );

public:


operator+( SmallInt& );

operator-( SmallInt& );

// ...

};
Необходими са само шест оператора понеже вградените



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

на операнд от тип int. Например, изразът


SmallInt si( 3 );
si + 3.14159
се изчислява на две стъпки:
1. Константата от тип double 3.14159 се преобразува до цялата

стойност 3.


2. Извиква се operator+(si,3), който връща стойност 6.
Ако също така желаем да бъдат поддържани побитови,

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

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

Вместо това ние ще предпочетем един начин за преобарзуване на

обекти на класа SmallInt към int.

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

дефинира набор от конвертори, които да могат да бъдат

прилагани над обектите на класа. За SmallInt ще дефинираме

конвертор за обект на SmallInt към тип int. Ето реализацията:
class SmallInt

{

public:



// conversion operator: SmallInt ==> int

operator int() { return value; }

// ...

private:


int value;

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

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

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

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

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

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

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

тип int. Например, изразът

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


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

284.
SmallInt si( 3 );
si + 3.14159;
сега се изпълнява на следните две стъпки:
1. Извиква се оператора конвертор на SmallInt, който дава

цялата стойност 3.


2. Цялата стойност 3 се преобразува до 3.0 е се събира с

константата от тип double 3.14159, кото се получава стойност

6.14159.
Слeдната програма илюстрира използуването на класа

SmallInt:


#include

#include "SmallInt.h"

SmallInt si1, si2;
main()

{

cout << "enter a SmallInt, please: ";



while ( cin >> si1 )

{

cout << "\n\nThank you.\n";



cout << "The value read is "

<< si1 << "\nIt is ";
// SmallInt::operator int() invoked twice

cout << (( si1 > 127 )

? "greater than "

: (( si1 < 127 )

? "less than "

: "equal to ")) << "127\n";


cout << "\nenter a SmallInt, please \

(ctrl-d to exit): ";

}

cout << "bye now\n";



}
Когато компилираме и изпълним тази програма ще получим

следния резултат:


enter a SmallInt, please: 127
Thank you.

The value read is 127

It is equal to 127

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


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

285.
enter a SmallInt, please (ctrl-d to exit): 126
Thank you.

The value read is 126

It is less to 127
enter a SmallInt, please (ctrl-d to exit): 128
Thank you.

The value read is 128

It is greater to 127
enter a SmallInt, please (ctrl-d to exit): 256

***SmallInt range error: 256 ***


Реализацията на класа SmallInt изглежда така:
#include
class SmallInt

{

friend istream&



operator>>(istream& is, SmallInt& s);

friend ostream&

operator<<(ostream& os, SmallInt& s)

{ return ( os << s.value ); }


public:

SmallInt(int i=0) : value(rangeCheck(i) ); }

int operator=(int i)

{ return( value = rangeCheck(i) ); }

operator int() { return value; }

private:


enum { ERRANGE = -1 };

int rangeCheck( int );

int error( int );

int value;

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

така:
istream& operator>>( istream& is, SmallInt& si )

{

int i;
is >> i;



si = i; // SmallInt::operator=(int)

return is;

}

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


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

286.
SmallInt::error( int i )

{

cerr << "\n***SmallInt range error: "



<< i << " ***\n";

return ERRANGE;

}
SmallInt::rangeCheck( int i )

{

// if any bits are set other than the first 8



// value is too large: report, then exit
if ( i & ~0377 )

exit( error( i ) ); // stdlib.h

return i;

}
Упражнение 6-27. Защо презаредимият оператор за вход

не е реализиран по следния начин?
istream& operator>>( istream& is, SmallInt & si )

{

return ( is >> si.value );



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

като SmallInt дефинира набор от неявни конвертори,

преобразуващи не-SmallInt типове в обекти от тип SmallInt.

Стандартните конвертори, ако са необходими, се прилагат над

типовете преди извикването на конструктора.

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

SmallInt(int), служи като оператор за преобразуване между типа

на аргумента и класа. SmallInt(int), например, преобразува

цели стойности в обекти на SmallInt.
extern f( SmallInt );

int i;
// need to convert i into a SmallInt

// SmallInt(int) accmplishes this

f( i );
При обръщението f( i ), i се преобразува в обект на SmallInt

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

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

287.
чрез извикване на SmallInt(int). Компилаторът конструира

временен обект на SmallInt и го изпраща на f().


// a temporary SmallInt Object is created

{

SmallInt temp = SmallInt(i);



f(temp);

}
Фигурните скоби, употребени в примера, показват

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

временен обект на SmallInt.

Ако е необходимо преди извикването на SmallInt(int) се

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


double d;

f( d );
става


{

// warning: narrowing conversion

SmallInt temp = SmallInt( (int)d );

f(temp);


}
Извикването на оператор конвертор се извършва само ако не са

възможни други преобразувания. Ако f() беше презаредима

функция, както е в следващия пример, SmallInt(int) нямаше да

бъде извикана.


f( SmallInt );

f( double );

int ival;
// matches f(double) by standard conversion

f( ival );


Аргументът може да бъде и от друг, дефиниран от

потребителя клас. Например,


class Token

{

public:



// ... public members

private:


SmallInt tok;

// ... rest of Token members

};

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


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

288.
// create a Smallint object from a Token

SmallInt::SmallInt( Token& t ) { /* ... */ }


extern f( Smallint& );

Token curTok;


main()

{

{ // invoke SmallInt( Token& );



f( curTok );

}
Оператори конвертори


Операторите за конвертиране, които са специални

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

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

типове. SmallInt, например, може да дефинира преобразуване на

обект на SmallInt в стойност от тип unsigned int:
SmallInt::operator unsigned int()

{

return( (unsigned)value );



}
Класът Token, дефиниран по-нататък в текста, може да

дефинира моного оператори конвертори:


#include "SmallInt.h"
class Token

{

public:



Token( char *nm, int v1 )

: val( v1 ), name( nm ) {}

operator SmallInt() { return val; }

operator char*() { return name; }

operator int() { return val; }

// .. rest of public members

private:

SmallInt val;

char *name;

};
Забележете, че операторите конвертори на Token за

SmallInt и int са еднакви. SmallInt::operator int() е приложен

неявно над обекта val на SmallInt в Token::operator int().

Например,

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


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

289.
#include "Token.h"
void f( int i )

{

cout << "\nf(int) : " << i;



}
Token t1( "integer constant", 127 );
main()

{

Token t2( "friend", 255 );


f( t1 ); // t1.operator int()

f( t2 ); // t2.operator int()

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

програма дава следния резултат:


f(int) : 127

f(int) : 255


Един оператор конвертор има следия общ вид:
operator ();
където се замества с определен вграден, произлязъл или

класов тип. (Операторите конвертори на масиви или функции не

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

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

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

например, е недопустима:


operator int( SmallInt& ); // error: nonmember
class SmallInt

{

public:



int operator int(); // error: return type

operator int( int ); // error: argument list

...

};
Програмистът може явно да извика оператор конвертор



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

Например,

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

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

290.
#include "Token.h"

Token tok( "function", 78 );


// function cast notation: operator SmallInt()

SmallInt tokVal = SmallInt( tok );


// type cast notation: operator char*()

char *tokString = (char *)tok;


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

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

приможен от компилатора. Например,
#include "Token.h"

extern void f( char *);

Token tok( "enumeration", 8 );

enum { ENUM }; // token constant


main()

{

f( tok ); // tok.operator char*();


// explicit functional cast notation

switch ( int(tok) ) // tok.operator int()

{

case ENUM:



{ // tok.operator SmallInt();

SmallInt si = tok;

// ...

}

}



}
Да допуснем, че желаният тип не съответствува точно на никой

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

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

преобразуване. Например,


extern void f( double );

Token tok( "constant", 44 );


// tok.operator int() invoked

// int ==> double by standard conversion

f( tok );
- не, ако желаният тип може да бъде получен чрез прилагане на

втори опрератор конвертор, дефиниран от потребителя върху

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

конвертор. (Ето защо Token дефинира както operator SmallInt()

така и operator int() ).

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


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

291.
Например, ако Token не предлага представител на operator int()

следното обръщение би било недопустимо:


extern void f(int);

Token tok( "pointer", 37 );


// without Token::operator int() defined,

// this call will generate a compile-time error

f( tok );
Ако Token::operator int() не е дефиниран,

преобразуването на tok към типа int ще изисква изивкването на

два оператора конвертори, дефинирани от потребителя.
Token::operator SmallInt();
ще преобразува tok в обект на SmallInt.
SmallInt::operator int();
ще завърши преобразуването до типа int.

Според правилото, обаче, могат да бъдат прилагани

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

ниво. Ако Token::operator int() не е дефиниран, обръщението

f(tok), изискващо аргумент от тип int, предизвиква грешка по

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

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

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


extern void f( int );

extern void f( SmallInt );

Token tok( "string constant", 3 );
f( tok ); // error: ambiguous
Token дефинира оператор конвертор както за SmallInt така и за

int: двете преобразувания са еднакво възможни. Обръщението

f(tok) е двусмислено, понеже компилаторът не може да избере

измежду двата конвертора. Затова обръщението предизвиква

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

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


// explicit convertion resolve ambiguity

f( int(tok) );


Ако Token::operator int() не е дефиниран, обръщението

няма да бъде двусмислено. Ще бъде изпълнен Token::operator

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

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

292.
SmallInt(). Фактът, че SmallInt дефинира оператор конвертор

int не се разглежда. При определяне на възможните

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

на опрераторите конвертори, дефинирани от потребителя.

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

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

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

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


class Token

{

public:



operator float();

operator int();

...

};
Обаче, ако и двата оператора конвертора са еднакво



приложими, те се отбелязват като двусмислени:
// error: both operators float() and int()

long lval = tok;


Двусмислие може да възникне и когато два класа

дефинират преобразувание помежду си. Например,


SmallInt::SmallInt( Token& );

Token::operator SmallInt();


extern void f( SmallInt );

Token tok( "pointer to class member", 197 );


// error: two possible user-defined conversions

f( tok );


В този случай съществуват два еднакво възможни начина

за преобразуване на tok в обект на SmallInt. Обръщението е

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

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

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

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


// explicit convertion resolves ambiguity

// Token::operator SmallInt() invoked

f( (SmallInt)tok );
Програмистът може да разреши двусмислието и чрез явно

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

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

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

293.
// explicit conversion resolves ambiguity

f( tok.operator SmallInt() ):


Забележете, че явното преобразуване в записа на функцията все

още е двусмислено.


// error: still ambiguous

f( SmallInt(tok) );


SmallInt(Token&) и Token::operator SmallInt() са еднакво

възможни интерпретации на обръщението.

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

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

обект на SmallInt се преобразува до стойност от тип int,

значението на това преобразувание е ясно. Когато идит обект на

String се преобразува до типа char*, значението на това

преобразувание също е ясно. И в двата случая съществува

съответствие едно-към-едно между вградените типове даннии и

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

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

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

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

{

public:



// guess which member is returned!

operator int();

private:

Int month, day, year;

};
Каква стойност трябва да бъде върната от опрератора конвертор

int на Date? Какъвто и избор да бъде направен по каквато и да

било добра причина, използуването на обекти на Date ще бъде

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

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

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


Упражнение 6-28. Какви оператори конвертори са

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

класа String?
extern int strlen( const char* );

const maxLen = 32;


String st = "a string";

int len = strlen( st );

if ( st >= maxLen ) ...

String st2 = maxLen;

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

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

294.
Упражнение 6-29. Разгледайте предимствата и

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

чрез
if ( st >= maxLen ) ...
Упражнение 6-30. Един интересен оператор конвертор за

BinTree връща обект на класа IntArray от стойности на INode

(Раздел 1.8 (стр. 51) съдържа дефиницията на класа IntArray).

Реализирайте следните два оператора конвертора:


BinTree::BinTree( IntArray& );

BinTree::operator IntArray();


Забележете, че реда за преглед на дървото трябва да бъде

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

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

форма:
BinTree::preorder( /* ??? */ )

{

if ( !node ) return;



node->visit( /* ??? *? ); // do work

if ( left ) left->preOrder( /* ??? */ );

if ( right ) right->preOrder( /* ??? */ );
Упражнение 6-31. Предишното упражнение предлага

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

IntArray и BinTree. Например,
extern BinTree bt;

extern IntArray ia;


if ( ia == bt ) ...
Редът на операциите е следният: Извиква се оператора конвертор

на BinTree за да приобразува bt в обект на класа IntArray.

Тогава се извиква оператора за равенство на IntArray за да

сравни двата обекта на класа IntArray. Какво ще се случи,

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

конвертори?


IntArray::IntArray( BinTree& );

IntArray::operator BinTree();

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

295.



Каталог: 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   ...   11   12   13   14   15   16   17   18   19




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

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