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



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

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

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

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

класа могат да бъдат инициализирани. Следната реализация на

конструктор, например, не е правилна:


class ConstRef

{

public:



ConstRef( int ii );

private:


int i;

const int ci;

int &ri;

};

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


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

244.
ConstRef::ConstRef( int ii )

{

// assignment



i = ii; // ok

ci = ii; // error: cannot assign to a const

ri = i; // error: ri is uninitialized

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

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

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

инициализационния списък. Например,
ConstRef::ConstRef( int ii )

: ci( ii ), ri( i ) // initialization

{ // assignment

i = ii;


}
Няма ограничение аргументът за инициализация да бъде

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

сложен израз. Например,
class Random

{

public:



Random( int i ) : val( seed ( i ) ) {}

int seed( int );

private:

int val;


}
Аргументът за инициализация на член обект на клас може

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


Word::Word( String &str, int cmt )

: name ( str ), occurs( cnt )

{}
String msg( "hello" );

Word greetings( msg );


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

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

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

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

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

класа Word, и synonym и antonym, обекти на класа String.

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

char* или от тип String&. Инициализационният списък трябва да

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

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


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

245.
class SynAntonym

{

public:



SynAntonym(char* s) : wd(s) {}

SynAntonym(char* s1, char* s2, char* s3)

: wd(s1), synonym(s2), antonym(s3) {}

~SynAntonym();

private:

String synonym;

Word wd;

String antonym;

};
SynAntonym sa1( "repine" );

SynAntonym sa2( "cause", "origin", "effect" );


Редът за извикване на конструкторите за sa1 и sa2 е

слединят:


1. Според реда на декларациите в тялото на калса се

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


String(); // synonym String member

String( "repine" ); // String member of wd

Word( "repine" ); // wd Word member

String(); // antonym String member

// sa1( "repine" );
String( "oridin" ); // synonym String member

String( "cause" ); // String member of wd

Word( "cause" ); // wd Word member

String( "effect" ); // antonym String member

// sa2( "cause", "oridin", "effect" );
2. Извиква се конструктора на външния клас.
Когато един член обект на клас съдържа член обект на

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

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

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

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

членовете обекти на клас. Ако има много обекти на класове

редът на обръщенията към деструкторите е обратен на реда на

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


Упражнение 6-2. Ето една схематична дефиниция на класа

Buffer:
#inlcude "String.h"

class Buf

{

public: // ...



private: String buf;

};

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


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

246.
Декларациите могат да приемат някоя от следните форми:
Stirng s1;
Buf();

Buf( 1024 );

Buf( s1 );
Реализирайте множеството на конструкторите и деструкторите.
Един важен абстрактен тип данни е бинарното дърво.

По-долу е представена схема на една реализация на класа

бинарно дърво за цели стойности. Реализирането на този клас ще

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

другите раздели на тази глава.
class BinTree;

class INode

{ // private class

friend class BinTree;

int val;

BinTree *left;

BinTree *right;

};
class BinTree

{

public:


// ... public intrface

private:


INode *node;

};
Едно бинарно дърво може да бъде празно (възелът му сочи 0) или

да сочи INode. Всеки INode се състои от три члена: цяла

стойност, ляв наследник и десен наследник. Всеки наследник е

празен или сочи към бинарно дърво.
Упражнение 6-3. Разгледайте предимствата и

недостатъците на дефинирането на класа INode като личен клас.


Упражнение 6-4. Дефинирайте конструктор(и) и

деструктор на класа INode.


Упражнение 6-5. Дефинирайте конструктор(и) и

деструктор на класа BinTree.


Упражнение 6-6. Разгледайте проекта, избран в

упражнение 6-4 и 6-5.

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

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

247.
6.2. Почленова инициализация
Има един случай, при който конструкторите, предложени

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

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

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


String vowel( "a" );

String article = vowel;


Инициализацията на article се извършва чрез

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

член на article. Това се нарича почленова инициализация.

Компилаторът изпълнява почленовото инициализиране чрез

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

форма.
X::X( const X& );


За класа String конструкторът е дефиниран някак така:
Stirng::String( const String& )

{

len = s.len;



str = s.str;

}
Реализирането на обект на клас чрез друг обект от

същия клас може да бъде необходимо в три случая:
1. Явно инициализиране на един обект на клас чрез

друг. Например,


// String::String( char* );

String color( "blue" );


// memberwise initialization generated

String mood = color;


2. Изпращане на обект на клас като аргумент на

функция. Например,


extern int count( Stirng s, char ch );
// local instance of s <== mood

int occurs = count( mood, 'e' );


3. Връщане на обект на клас от функция. Например,

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


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

248.
extern String sub( String&, char, char );
main()

{

String rriver( "mississippi" );



cout << river << " "

<< sub( river, 'i', 'I' ) << "\n";

}
Нито изпращането на аргумент псевдоним, нито връщането

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

Това се дължи на факта, че изпращането-чрез-псевдоним за

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

на обекта. (Раздел 3.6. (стр. 118) разглежда

изпрашането-чрез-псевдоним).

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

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

класове, обаче, не се копират; а по-скоро почленовото

инициализиране се прилага рекурсивно. Например, класът Word

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

класа String, name. Ето дефинициите на двата обекта на Word:
Word noun( "book" );

Word vern = noun;


verb се инициализира на сладните сттъпки:
1. Членът occurs се инициализира със стойността на

noun.occurs.


2. Членът name се инициализира почленово чрез вътрешно

генерирания конструктор за класа String.


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

недостатъчно. Фиг. 6.1 илюстрира паметта, отделена за count и

verb. Съществуват дава проблема:
1. count на noun не трябва да бъде копиран в count на

verb. Фактически, двете стойности са разделени.

Почленовият механизъм по подразбиране нарушава

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


2. Членът str на noon и verb адресира една и съща

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

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

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

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

или дефинират деструктор. Това се дължи на факта, че

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

е инициализиран почленово, а не "конструиран".

Както показва фиг. 6.1, това означава, че паметта,

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

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

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


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

249.
-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇя

у int occurs 10 у

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇф

у у


у char *str ЇЇїЇЇЇя

у String name ---------- у у

у int len 4 у у -ЇЇЇЄЇЇЇЄЇЇЇЄЇЇЇЄЇЇЇЇя

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ- єЇЇЇЇЇф'b'у'o'у'o'у'k'у'\0'у

Word noon("book"); у ЁЇЇЇёЇЇЇёЇЇЇёЇЇЇёЇЇЇЇ-

у

у



-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇя у

у int occurs 10 у у

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇф у

у у у


у char *str ЇЇїЇЇЇ-

у String name ---------- у

у int len 4 у

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-

Word verb = noon;
Фигура 6.1 Почленова инициализация

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

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

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

Този проблем може да бъде решен, като проектантът на класа

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

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

Специалният конструктор X(const X&)


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

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

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

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

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

на конструктора X(const X&). Ако такъв конструктор е явно

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

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

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

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

250.
String::String( const String& s )

{

len = s.len;



str = new char[ len + 1 ];

strcpy( str, s.str );

}
String(const String& ) се извиква винаги, когато един обект на

String се инициализира чрез друг. Всеки член str ще адресира

различна област от паметта.
Упражнение 6-7. Реализирайте конструктор Screen(const

Screen&) за класа Screen, дефиниран в глава 5. Дайте пример за

три случая, в които се вика този конструктор.
Упражнение 6-8. Реализирайте конструктор IntList(const

IntList&) за класа IntList, дефиниран в глава 4. Дайте пример

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

X(const X&) и обекти членове на клас


В този подраздел, ние ше разгледаме два примера на

X(const X&) за класове, съдържащи обекти членове на клас:


1. Външният клас не дефинира представител на X(const

X&), но класът член дефинира.


2. Двата класа дефинират представител на X(const X&).
В първия случай, илюстриран чрез дефиницията на класа

Word, Word е без Word(const Word&). към члена клас, String,

обаче, е добавена дефиниция на String(const String&).
class Word

{

public:



Word( char *s, int cnt = 0 )

: name(s), occurs(cnt) {}

Word( String& s, int cnt = 0 )

: name(s), occurs(cnt) {}

private:

int occurs;

String name;

};

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


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

251.
Инициализацията на един обект на Word чрез друг използува

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

String, обаче, се инициализира чрез извикване на String(const

String&). Например,


String mystery( "rosebud" );

Word resolve( mystery );


extern search( Word wd );

search( resolve );


String(const String&) се извиква за инициализиране на члена

name от resolve и на члена name от локалното копие на wd.

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

всеки член обект на клас. За всеки член обект на клас, който

дефинира представител на X(const X&), обаче, се извиква този

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

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

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

Word(const Word&). Представителят на Word отговаря за

инициализацията на членовете си. Преди да илюстрираме

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

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


// this implementation is incorrect

Word::Word( const Word& w )

{

occurs = 0;



name = w.name;

}
Този представител неправилно инициализира члена name от класа

String. Нека да разгледаме следния пример постъпково:
Word weather( "warm" );

Word feeling = wither;


Стъпките по инициализирането са следните:
1. feeling се разпознава като инициализиран с обекта

на класа Word(const Word&). Има ли дефиниран

представител на Word(const Word&)? Ако има, той се

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

инициализиране. Word(const Word&) е намеран.
2. Има ли инициализационен списък? Не.
3. Има ли някакви членове обекти на клас? Да. name е

обект на класа String.


4. В класа String дефиниран ли е конструктор, който не

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

грешка по време на компилация; иначе той се

извиква.


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

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

252.
5. Извиква се String() за инициализирането на

feeling.name.


6. Извиква се Word(const Word&). Изпълнява се

присвояването name = w.name. По подразбиране, както

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

клас се извършва чрез почленово присвояване (вж.

раздел 6.3 (стр. 266) за подробности). String(const

String&) никога не се извиква.


Това е втори пример, при който е важно

разграничаването на фазите на инициализация и присвояване.

(Раздел 6.1 (стр. 243) разглежда първия - инициализацията на

const и на членове псевдоними на клас). За да бъде извикан

конструктора на обект на String name трябва да бъде

инциализиран с w.name. Това означава, че name трябва да бъде

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

конструктор на обект на Word има вида:


Word::Word( const Word& w )

: name ( w.name ) // initialization

{ // assignment

occurs = w.occurs;

}
Накратко, ако един външен клас не дефинира

представител на X(const X&), всеки член обект на клас се

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

на X(const X&), този представител ще бъде извикан. Ако

външният клас не дефинира представител на X(const X&), обаче,

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

клас поради своя инициализационен списък.
Упражнение 6-9. Реализирайте Buf(const Buf&) ( Упр.

6-2).
Упражнение 6-10. Реализирайте представителите на

конструкторите INode(const INode&) и BinTree(const BinTree&).

Обобщение на конструкторите и деструкторите


Специалният механизъм на конструкторите и

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

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

презареждани за да предлагат набор от опции за инициализация;

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

За членовете обекти на класове, които имат собствени

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

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

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

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

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

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

253.
Всеки конструктор може да определи инициализационен

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

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

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

данни, които не са обекти на клас, както и за const reference

членове на клас.

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

обект на клас: когато този обект се инициализира чрез

съществуващ обект на клас. Тогава се използува почленовата

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

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

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

обект.

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



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

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

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

инициализиране на обект X(const X&), който да обработва този

случай. Инициализацията на един обект на клас чрез друг

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

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

6.3. Презареждане на оператор


В предишния раздел дефинирахме член данните и член

функциите, необходими за инициализирането и освобождаването на

обекти от класа String. Какви други функции трябва да бъдат

дефинирани за класа Stirng?

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

класа Stirng: Дали един низ е празен? Равен ли е един низ на

друг? Дали един низ е подниз на друг? Потребителят трябва също

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

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

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

Кодирането на използуването на един низ може да изглежда така:
String inBuf;

while ( readString( cin, inBuf ))

{

if ( inBuf.isEmpty() ) return;



if ( inBuf.isEqula( "done" )) return;
switch( inBuf.index(0) ) { /* ... */ }

cout << "String is ";

writeString( cout, inBuf );

}
Използуването на един клас не е толкова лесно колкото

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

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

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

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

операторите, заменящи именуваните операции на String:

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


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

254.
String inBuf;
while ( cin >> inBuf )

{

if ( !inBuf ) return;



if ( inBuf == "done" ) return;
switch( inBuf[ 0 ] ) { /* ... */ }

cout << "String is " << inBuf;

}
В останалата част на този раздел ще реализираме набор от

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

програмиране за класа String.

Преглед на операторите за презареждане


Един проектант на клас може да предложи определен

набор от оператори за работо с обекти на клас. Един оператор

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

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

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

данни. Един оператор функция се дефинира по същия начин, както

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

се състои от ключовата дума operator, следвана от някой

предварително дефиниран от С++ оператор. Например,
String& String::operator=( const String& s )

{ // assign one String object to another

len = s.len;

delete str; // deallocate existing array

str = new char[ len + 1 ];

strcpy( str, s.str );

return *this;

}
При всяко присвояване на един обект от тип String на друг ще

бъде викан оператора за присвояване на String.
#include "String.h"
String article( "the" );

String common;


main()

{ // String::operator=()

common = article;

}
Оператор функциите на един клас могат да бъдат презареждани

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

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

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

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

да присвояват на обект от тип String стойност от тип char*.
class String

{

public:



String &operator=( const String& );

String &operator=( const char* );

// ...

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



символни низа за равенство. Ето нашия оператор за равенство на

класа String, който работи с два String обекта:


String::operator==( String& st )

{

// strcmp return 0 if both strings are equal



// operator== returns 1 for equality

return( strmcp( str, st.str ) == 0 );

}

Дефиниране на оператор функции


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

дефинирани в С++ оператори. Проектантът на един клас не може

да въведе нов оператор ("**", например, за повдигане на

степен). Таблица 6.1 показва операторите, които могат да бъдат

презареждани.
-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇя

у Презаредими оператори у

єЇЇЇЇЇЄЇЇЇЇЄЇЇЇЇЄЇЇЇЇЄЇЇЇЇЇЄЇЇЇЇЇЇЄЇЇЇЇЇЇЇЇЄЇЇЇЇЇЇф

у + у - у * у / у % у ^ у & у | у

у ~ у ! у ' у = у < у > у <= у >= у

у ++ у -- у << у >> у == у != у && у || у

у += у -= у /= у %= у ^= у &= у |= у <<= у

у >>= у [] у () у -> у ->* у new у delete у у

ЁЇЇЇЇЇёЇЇЇЇёЇЇЇЇёЇЇЇЇёЇЇЇЇЇёЇЇЇЇЇЇёЇЇЇЇЇЇЇЇёЇЇЇЇЇЇ-

Таблица 6.1 Презаредими оператори


Предварително дефинираното значение на един оператор

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

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

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

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

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

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

операции. Програмистът може да дефинира оператори само за типа

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

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

Предварително дефинирания приоритет на операторите

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


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

256.
(раздел 2.9 (стр. 79) разглежда това) не може да бъде

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

оператора,
x == y + z;
винаги ще изпълнява operator+ приди operator==. Както и при

предварително дефинираните оператори приоритетът може да бъде

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

Предварително дефинираната "арност" на оператора


Каталог: 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
отнасят до администрацията

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