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



страница13/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   9   10   11   12   13   14   15   16   ...   19
Глава 6: Член функции на клас
Тази глава разглежда следните три категории член

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


1. Конструктори и деструктори за автоматична

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


2. Презаредими оператор функции, които могат да бъдат

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

синтаксиса за запис на оператор по-скоро, отколкото

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

извикване на член функцията isEqual() на Screen:
if ( myScreen.isEqual(yourScreen) )
презаредимостта на операторите позволява на

потребителите на класа Screen да напишат следното

еквивалентно извикване:
if ( myScreen == yourScreen )
Освен това един клас може да се заеме и с

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

свои членове представители на операторите new и

delete.
3. Оператори за конвертиране, които да дефинират

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

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

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

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


Извикването на тези специални член функции в общия

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

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

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

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

6.1. Инициализация на клас


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

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

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

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

фигурни скоби. Например,

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


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

232.
class Word

{

public:



int occurs;

char *string;

};
// explicit member initialization

Word search = { 0, "rosebud" };


Най-общо казано, С++ поддържа механизъм за автоматична

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

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

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

отделя памет чрез оператора new. Конструкторът е създадена от

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

ето един конструктор за Word:
class Word

{

public:



Word( char*, int = 0 ); // constructor

private:


int occurs;

char *string;

};
#include

inline Word::Word( char *str, int cnt )

{

string = new char [ strlen(str) + 1 ];



strcpy( string, str );

occurs = cnt;

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

явно да връща стойност. Иначе, дефиницията на един конструктор

е същата, както и дефиницията на обикновените член функции. В

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

тип char*. Може да бъде добавен и опционния втори аргумент от

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

Word може да бъде дефиниран при наличието на този конструктор.
#include "Word.h"
// Word::Word( "rosebud", 0 )

Word search = Word("rosebud");


// Word::Word( "sleigh", 1 )

Word *ptrAns = new Word( "sleigh", 1 );

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

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

233.
main()

{

// shorthand constructor notations


// Word::Word( "CitizenKane", 0 )

Word film( "CitizenKane", 0 );


// Word::Word( "Orson Welles", 0 )

Word director = "Orson Welles";

}

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


Един от най тежко използуваните типове данни е string.

В С++, обаче, низовете са произхождащ тип (масив от сиимволи)

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

присвояване или сравнение, например). Типът string е един

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

Ние ще използуваме проекта на класа Sctring за да илюстрираме

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

презаредимите оператори. Започваме с по-подробно разглеждане

на конструкторите.

Един конструктор може да бъде идентифициран като за

име му бъде дадено името на класа. Той може да бъде

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

инициализациии. Класът String, например, съдържа два члена

данни:
1. str, от тип char*, който адресира масива от символи

на низа.
2. len, от тип int, който съдържа дължината на

символния масив, сочен от str.


Нека да декларираме два конструктора за String, един, който

инициализира str и втори, който инициализира len.


class String

{

public:



String( int );

String( char* );

private:

int len;


char *str;

};
Дефиницията на един обект на класа отделя памет,

необходима нестатичните член данни, дефинирани от класа.

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

дефиницията на нашия първи конструктор на String:

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


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

234.
String::String( char *s )

{

// #include



len = strlen( s );

str = new char[ len + 1 ];

strcpy( str, s );

}
Няма нищо особено сложно в дефиницията на този

конструктор. Силата й се дължи на мехонизма на класовете,

който я извиква неявно за всеки обект на клас, който

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

низ. strlen() и strcpy() са функции за работа с низове от

стандартната С++ библиотека. Ние ще осигурим поддържане на

подобни функции за нашия клас String. Добре би било разгледаме

защо str не присвоява просто адреса на s:
str = s;
а й отделя динамично памет, в която копира s. Основната

причина е, че ние не можем да знаем дали е подходящо

разположението на паметта, отделена на низа, който s съдържа:
- Ако низът е локално разположен, т.е. в работния

стек, отделената памет ще бъде освободена когато

блокът, който я дефинира приключи работа - всяко

по-нататъщно използуване на str ще бъде погрешно.

Например,
String *readString()

{

// example of a string with local extent



char inBuf[ maxLen ];

cin >> inBuf;

String *ps = new Sting( inBuf );

return ps;

}
String *ps = readString();
- Ако низът е динамично разположен, т.е. отделена му е

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

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

прилагането на оператора delete върху памет, която

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

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

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

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

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

особено внимание от страна на програмиста. Отново ще се върнем

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

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

Ето дефиницията и на втория конструктор на String:

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


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

235.
String::String( int ln )

{

len = ln;



str = new char[ len + 1 ];

str[0] = '\0';

}
Решението да се поддържа дължината на низа като отделно поле

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

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

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

вариант се основава на две неща: Първо, дължината на един низ

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

предпочита пред спестяването на място ( това трябва да е

доказано от практиката ). Второ, обектите на String ще бъдат

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

на буфери с фиксирана дължина.

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

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

типа на дефиницията. Следните три дефиниции на обекти на

String не са валидни. В първия случай, не са зададени

аргументи. Във втория аргументът е от неправилен тип, а в

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


int len = 1024;
String myString; // error: no arguments

String inBuf( &len ); // error: bad type: int*

String search("rosebud", 7); //error: two arguments
Съществуват две форми за синтактичен разбор на

аргументите на конструктора - явна и съкратена. Следните форми

за дефиниране на обекти на класа String са правилни:
// explicit form: reflects actual invocation

String searchWord = String( "rosebud" );


// abbreviated form: #1

String comonWord( "the" );


// abbreviated form: #2

String inBuf = 1024;


// use of new required explicit form:

String *ptrBuf = new String( 1024 );


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

след като е отделена необходимата памет. Ако new не успее да

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

Ууказателят към класа получава стойност 0. Например,

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

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

236.
String *ptrBuf = new String( 1024 );

if ( ptrBuf == 0 )

cerr << "free store exhausted\n";
Би било полезно обекти на String да може да бъдат

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


String tmpStr;
Това може да бъде направено или чрез задаване на стойности по

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

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

подразбиране. Конструкторът по подразбиране ще дефинира празен

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

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

индивидуалните обекти на клас. Ако са дадени по-малко

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

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

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

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

String може да изглежда така:


#include "String.h"
String::String()

{

// default constructor



len = 0;

str = 0;


}
Една чест срещана програмна грешка е следната:
String st();
Този израз не дефинира обект st от класа String, инициализиран

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

st() без аргументи и тип за връщане обект на класа String.

Следват две правилни дефиниции на обект на класа String:


String st;

String st = String();


Упражнение 6-1. Дефинирайте един конструктор на

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


String s1( "rosebud", 7 );

String s1( "rosebud", 8 );

String s2( "", 1024 );

String s3("The Raw and the Cooked" );

String s4;

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


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

237.
Конструктори и скриване на информация
Всеки конструктор има такова ниво на дастъп до

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

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

String като буфер Stirng::strin(int) може да бъде деклариран

като личен:
class Stirng

{

friend class Buf;



public:

String( char* );

String();

private:


String( int );

// ... rest of Stirng class

};
Единствено член функциите на String и приятелите на

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

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

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

получават аргументи или пък аргументът им е от тип char*.
f()

{

// ok: String::String( char* ) is public



String search( "rosebud" );
// error: String::String( int ) is private

String inBuf( 1024 );

...

}
Buf::in()



{

// ok: String::String( char* ) is public

String search( "rosebud" );
// ok: String::String( int ) is private

// Buf is a friend to String

String inBuf ( 4096 );

...


}
Личен клас е клас без публични конструктори.

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

обекти на класа. Класът IntItem, деклариран в Раздел 4.2 (стр.

145) е един пример за личен клас. Обектите на класа IntItem

могат да бъдат дефинирани само от класа IntList, който е

деклариран като приятелски на IntItem.

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

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

238.
Деструктори
С++ поддържа един механизъм, допълващ този на

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

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

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

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

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

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

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

самият той не е обект на клас. Деструкторът на String се

дефинира така:


class String

{

public:



~String(); // destructor

...


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

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

преди него се запише тилда ("~"). Деструкторът не може да

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

презареждан). Той не трябва да дефинира тип за връщане или да

връща стойност. Деструкторът на класа String се дефинира така:


String::~String() ( delete str; }
Да припомним факта, че един конструктор фактически не

отделя памет. По-скоро той служи за инициализиране на току-що

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

това на практика деструкторът също не освобождава памет. Той

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

на паметта, което става когато обектът излезе от обхват. В

този случай, понеже str адресира памет, отделена чрез

оператора new деструкторът на String явно я изтрива. Паметта

на члена на класа len, обаче, не изисква никаква специална

обрабатка.

Няма ограничения върху това какво може да бъде правено

от един деструктор. Една обща техника за тестване на програми,

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

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


String::~String()

{

#ifdef DEBUG



cout << "String() "

<< len << " " str << "\n";

#endif


delete str;

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

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

класа да излезе от обхват.

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

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

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

обекти на клас, които са излезли от обхват. По-скоро,

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

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

Например,
#include "String.h"

String search( "rosebud" );


f()

{

// would not want destructor applied to p



String *p = &search;
// would want destructor applied to p

String *pp = new String( "sleigth" );


// ... body of f()
// Sting::String() invoke for pp

delete pp;

}
Ако указателят, към който е приложен операторът delete, не

адресира обект на клас (т.е. има стойност 0), деструкторът не

се извиква. Не е необходимо да се пише
if ( pp != 0 )

delete pp;


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

нужда да извика деструктора явно. Това е когато той иска да

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

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

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

Например,


#include

#include

#include

struct inBuf

{

public:


inBuf( char* );

~inBuf();

private:

char *st;

int sz;

};

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


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

240.
inBuf::inBuf( char *s )

{

st = new char [ sz = strlen(s)+1 ];



strcpy( st, s );

}
inBuf::~inBuf()

{

cout<<"inBuf::~inBuf(): " << st <<"\n";



delete st;

}
char *pBuf = new char[ sizeof( inBuf ) ];


main()

{

inBuf *pb = new (pBuf)



inBuf( "free store inBuf #1" );

pb->inBuf::~inBuf(); // explicit destructor call


pb = new (pBuf) inBuf( "free store inBuf #2" );

pb->inBuf::~inBuf(); // explicit destructor call


// ...

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

се получи следния резултат:
inBuf::~inBuf(): free store inBuf #1

inBuf::~inBuf(): free store inBuf #2


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

на пълното му име. Например,


pb->inBuf::~inBuf(); // correct

pb->~inBuf(); // error

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

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

и tbl2 дефинират масиви от по 16 обекта на класа String:
const int size = 16;

String tbl[ size ];

String *tbl2 = new String[size];
Достъпът до отделните елементи се осъществява, като се

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

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

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


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

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

индексния оператор. Ето един пример:


while ( cin >> tbl[ i ] )

tbl[i].display();


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

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

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

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

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

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

така и съкратения запис.
Stirng ar1[] = { "phoenix", "crane" };

String ar2[3] = { String(), String(1024),

String("string")};

String ar3[2] = { 1024, String( 512 ); };


Screen as[] = { Screen(24, 80,'#') };
Клас, който дефинира конструктор по подразбиране (т.е.

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

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

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

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

елемент на масиива. Масив, за който е отделена памет от

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

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

конструктор без аргуменнти.

Преди tbl2 да излезе от обхват е необходимо явното

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

Обаче, простото написване на


delete tbl2;
не е достатъчно понеже деструкторът на String се прилага само

за началния елемент на tlb2. delete не може да знае, че tbl2

не сочи един обект от String, а масив от такива обекти.

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

масива, който tbl2 адресира. Това може да бъде направено така:
delete [ size ] tbl2;
Сега деструкторът на String ще бъде извикан за всеки от

елементите на tbl2.

Членове обекти на клас
След като вече сме въвели класа String, нека да

дефинираме отново класа Word като заменим члена char* с член

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

публичен интерфейс.

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

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

242.
class Word

{

public:



Word();

Word( char*, int = 0 );

Word( String&, int = 0 );

private:


int occurs;

String name;

};
Сега за всеки обект на Word трябва да бъдат викани два

конструктора - този на самия клас и другия за члена от клас

String. В този случай трябва да бъдат разгледани два въпроса:
1. Дефиниран ли е ред за извикване на конструкторите?

И ако е дефиниран, какъв е?


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

конструктора на члена от тип клас?


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

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

преди конструктора на класа, който ги съдържа. В слъчай, че

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

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

в декларацията. (Редът на деструкторите е обратен).


// first String::String( char * )

// then Word::Word( char * )

Word flower( "iris" );
Аргументите се изпращат до конструкторите чрез

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

име/аргумент, отделени със запетаи. Например,
Word::Word( char *s, int cnt ) : name( s )

{

occurs = cnt;



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

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

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

на член може да се явява само в дефиницията на един

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

предходния пример на name се изпраща указателя към низ s,

който на свой ред се подава като аргумент на конструктора на

String. За член данните от вградените типове също могат да

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

в следния пример occurs се инициализира със стойността на cnt:

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

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

243.
Word::Word( char *s, int cnt )

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


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

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

конструктора е празно няма фаза на присвояване. Например,
class Simple

{

public:



Simple( int, float );

private:


int i ;

float f;


};
Simple::Simple( int ii, float ff )

: i(ii), f(ff) // initialization

{} // assignment
Фазата на присвояването започва с изпълнението на

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

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

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

инициализационната фаза явна. Няма да има инициализационна

фаза в случай, че конструкторът на Simple е дефиниран така:


Simple::Simple( int ii, float ff )

{

i = ii; f = ff; // assignment phase



}
В повечето случаи разгранираването на фазите за

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

конструктор е прозрачно за програмиста. Обрабатването на const


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




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

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