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



страница11/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   7   8   9   10   11   12   13   14   ...   19
Глава 5: Класът в С++
Класовият механизъм в С++ позволява на потребителите

да дефинират техни собствени типове данни. Тези типове могат

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

като класа IntArray, дефиниран в глава 1. Класовете могат също

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

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

класа BitVector. Класовете типично се използуват за дефиниране

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

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

computer Task, класа за терминален дисплей Screen, класа

Employee или ZooAnimal. Възможните класови типове са

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

известен брой класове.

Всеки С++ клас има четири атрибута, свързани с него:


1. Съвкупност от членове данни (data members),

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

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

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

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

член функции. Те се наричат интерфейс на класа.


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

да бъдат определени като private, protected или public. Тези

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

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

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

представянето са public. Типът на public/private спецификации

се свързва с термина информационно скриване. За вътрешно

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


4. Име на класа (tag name), което служи за типов

спецификатор на клас, дефиниран от потребителите. Име на клас

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

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

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

напише:
Screen myScreen;

Screen *tmpScreen = &myScreen;

Screen& copy( const Screen[] );


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

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

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

абстрактните типове данни в С++.

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

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

178.
5.1. Дефиниция на клас
Всяка дефиниция на клас има две части: глава,

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

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

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

Например,
class Screen { /* ... */ }

class Screen { /* ... */ } myScreen, yourScreen;


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

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

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

Член данни


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

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

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

дефинира представянето си така:


class Screen

{

short height; // number of Screen rows



short width; // number of Screen columns

char *cursor; // current Screen position

char *screen; // screen array (height*width)

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

два члена short или два члена char* да се декларират отделно.

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


class Screen

{

/*



* height and width refer to row and column

* cursor points to current Screen position

* screen addresses array height*width

*/

short height, width;



char *cursor, *screen;

};
Изобщо, ако няма причина да бъде направено друго,

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

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

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

Например,

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

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

179.
class StackScreen

{

int toptack;



void (*handler)(); // handles exceptions

Screen stack[ STACK_SIZE ];

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

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

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

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

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

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

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

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

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

предварителна декларация:


class Screen; // forward declaration

class StackScreen

{

// pointer to STACK_SIZE Screen objects



int topStack;

Screen *stack;

void (*handler)();

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

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

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

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

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


class LinkScreen

{

Screen window;



LinkScreen *next;

LinkList *prev;

}

Член функции


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

широк кръг от операции върху обектите от класа Screen. Ще бъде

необходим един набор от операции за движение на курсора. Също

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

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

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

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

екрана. Този набор от операции за обработване на обект от тип

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

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

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

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

Например,

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


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

180.
class Screen

{ public


void home();

void move( int, int );

char get();

char get( int, int );

void chackRange( int, int );

// ...


};
В тялото на класа може също да бъде поставена и

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


class Screen

{ public:

void home() { cursor = screen; }

char get() { return *cursor; }

// ...

}
home() позиционира курсора в горния ляв ъгъл на



екрана. get() връща стойността на текущата позиция на курсора.

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

обработват като функции inline.

Член функциите, които имат повече от един - два реда,

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

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

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

checkRange():


#include "Screen.h"

#include

#include
void Screen::checkRange( int row, int col )

{ // validate coordinates

if ( row < 1 || row > height ||

col < 1 || col > width )

{

cerr << "Screen coordinates ( "



<< row << ", " << col

<< " ) out of bounds.\n";

exit( -1 );

}

}
Член функция, която е декларирана вън от тялото на



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

Например, следният фрагмент дефинира move() като inline член

функция на Screen:

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


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

181.
inline void

Screen::move( int r, int c )

{ // move cursor to absolute position

checkRange( r, c ); // valid address?

int row = ( r-1 )*width; // row location

cursor = screen + row + c - 1;

}
Член функциите могат да бъдат разграничени от

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


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

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

докато, докато обчайните функции имат достъп само до

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

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

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


- Член функциите са дефинирани в обхвата на класа си;

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

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

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

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

функции могат да се описват само в един и същ обхват.

Описаните по-долу втори представители на get(), например,

нямат никаква връзка с глобалната функция get(), която не е

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

клас.
inline char

Screen::get( int r, int c )

{

move( r, c ); // position cursor



return get(); // other Screen get()

}

Скриване на информация


Често се случва така, че вътрешното представяне на

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

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

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

всички класови обекти Screen се дефинирани с размерност 24 х

80. В този случай следното представяне на класа Screen ще бъде

не така гъвкаво, но по-ефективно:

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


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

182.
const Height = 24;

const Width = 80;

class Screen

{

char screen[ Height ][ Width ];



short cursor[2];

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

но интерфейсът й (т.е. сигнатурата и типа за връщане) ще

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

вътрешното представяне за потребителите на този клас?
- Всяка програма, която е имала директен достъп до

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

не работи. Необходимо е да се открият и напищат

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

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

използувани отново.


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

обекти от тип Screen само върху член функциите им не

се нуждае от промяна на текста. Необходимо е, обаче,

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


Скриването на информацията е един формален механизъм

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

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

чрез public, private и protection раздели в тялото на класа.

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

членове; тези, декларирани в private и protected стават лични

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

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

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

до член функции, имащи значение за дефиниране на

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

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

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

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

използувани в произлезлия клас IntArrayRC, описан в

глава 1. Пълното описание на защитените членове е

отложено за глава 7, в която се въвеждат

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

наследствеността).
- Един личен член може да бъде достъпен само

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

(Вж. раздел 5.5 (стр. 200) по-нататък в тази глава

за подробности относно приятелите). Всеки клас,

който желае да скрие информация, декларира член

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


Следните дефиниции на Screen определят публичните и

личните му части:

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

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

183.
class Screen

{

public:



void home() { move( 0, 0 ); }

char get() { return *cursor; }

char get( int, int );

inline void move( int, int );

// ...

private:


short heigth, width;

char *cursor, *screen;

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

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

на тялото на класа.

Един клас може да съдържа много много секции, означени

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

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

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

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

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

5.2. Класови обекти


Предходната дефиниция на Screen не предизвикваше

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

на всеки класов обект се отделя памет за класа. Дефиницията:
Screen myScreen;
например, отделя памет, достатъчна да побере четерите член

данни на класа Screen.

Обектите от един и същ клас могат да бъдат

инициализирани и присвоявани един на друг. По подразбиране,

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

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


// bufScreen.height = myScreen.height

// bufScreen.width = myScreen.width

// bufScreen.cursor = myScreen.cursor

// bufScreen.screen = myScreen.screen

Screen bufScreen = nyScreen
По подразбиране класовете обекти се изпращат по

стойност като аргументи на функции и типове за връщане на

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

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

чрез адресния оператор ("&"). Например,

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


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

184.
Screen *ptr = new Screen;

myScreen = *ptr;

ptr = &bufScreen;
Вън от обхвата на класа на операторите за избор на

член е необходимо да имат достъп или до член данните или до

член функциите на класа. Селекторът на обект клас (".") се

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

указател към обект клас ("->") се използува с указател към

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


#include "Screen.h"

isEqual( Screen& s1, Screen *s2 )

{ // return 0 if not equal, 1 if equal
if ( s1.getHeigth() != s2->getHeigth() ||

s1.getWidth() != s2->getWidth() )

return 0; // not equal
for ( int i = 0; i < s1.getHeigth(); ++i)

for ( int j = 0; j < s2->getWidth(); ++j)

if (s1.get( i, j ) != s2->get( i, j ))

return 0; // not equal


// still hehre? then screens are equal

return 1;

}
isEqual() е една нечлен функция, която сравнява два

Screen обекта за еквивалентност. isEqual() няма права на

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

на публичните член функции на класа Screen.

getHeigth() и getWidth(), наречени функции за достъп,

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

класа. Тяхната реализация следва. За да бъдат по-ефективни те

са дефинирани като inline:


class Screen

{ public:

int getHeigth() { return heigth; }

int getWidth() { return width; }

// ...

private:


short heigth, width;

// ...


};
Членовете на класа могат да бъдат достъпни директно в

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

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

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


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

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

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


#include "String.h"

#include


void Screen::copy( Screen& s )

{ // copy one Screen object with another


delete screen; // free up existing storage

heigth = s.heigth;

width = s.width;
screen = cursor = new char[heigth*width + 1];

strcpy( screen, s.screen );

}
Упражнение 5-1. copy() дава такъв размер на обекта

начначение Screen, какъвто е размера на обекта Screen, който

се копира. Реализрайте copy() така, че да позволява размерите

на обектите източник и назначение Screen да бъдат различни.


Упражнение 5-2. Относно символите, съдържащи се в

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

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

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

функция strcpy(). Какво е това предположение? Реализирайте

copy() отново така, че да не зависи от това предположение.

5.3. Член функции на клас
Член функциите предлагат набор от операции, които

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

нарича публичен интерфейс на класа. Дали класът е сполучлив

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

член функции. Този раздел разглежда член функциите, необходими

на класа Screen. Той е разделен на следните четири подсекции:

управляваши функции, функции за реализация, помощни функции и

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

езика С++. По-скоро те изразяват един метод за мислене относно

типовете член функции, от които най-общо се нуждае един клас.

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

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

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

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

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

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

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

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

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

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

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

конструктора на класа Screen:


Screen::Screen( int high, int wid, char bkground )

{ // Screen initializer function: constructor


int sz = high * wid;

heigth = high; width = wid;

cursor = screen = new char[ sz + 1 ];
char *ptr = screen;

char *endptr = screen + sz;

while ( ptr != endptr ) *ptr++ = bckground;

*ptr = '/0'; // end of screen marked by null

}
Декларацията на конструктора в тялото на Screen предлага

подразбиращи се стойности за аргументите high, wid и bkground.


class Screen

{

public:



Screen ( int = 8, int = 40; char='#' );

// ...


};
Всеки деклариран обект от тип Screen автоматично се

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


Screen s1; // Screen(8,40,'#')

Screen *ps = new Screen( 20 ); // Scrren(20,40,'#')


main()

{

Screen s(24,80,'*'); // Screen(24,80,'*')



// ...

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

управляващи функции.

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


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

187.
Функции за реализация
Второто множество от член функции, наречени функции за

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

клас. Например, от Screen се очаква да поддържа такива

движения като home() и move(). Допълнителни изисквания към

движението на курсора са forward(), back(), up() и down().

Член функците forward() и back() местят курсора по един

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

се движи кръгово.


inline void Screen::forward()

{

// advance cursor one screen element



++cursor;
// check for bottom of screen; wrapround

// bottom of screen is null element

if ( *cursor == '\0' )

home();


}
inline void Screen::back()

{ // move cursor backward one screen element


// check for top of screen; wrapround

if ( cursor == screen )

bottom();

else


--cursor;

}
bottom() е от функциите за реализация и позиционира

курсора в последната колона на екрана.
inline void Screen::bottom()

{

int sz = width*height - 1;



cursor = screen + sz;

}
up() и down() местят курсоро нагоре и надолу по един

ред от екрана. При достигане на най-горния и най-долния ред на

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

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

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


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

188.
const char BELL = '\007';

void inline Screen::up()

{ // move cursor up one row of screen

// do not wraparound; rather, ring bell


if ( row() == 1 ) // at top?

cout.put( BELL );

else

cursor -= width;



}
void inline Screen::down()

{

if ( row() = heigth ) // at bottom?



cout.put( BELL );

else


cursor += width;

}
Упражнение 5-3. Към движенията на курсора могат да

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

думата е ограничена с интервали. Реализирайте wordForward().


Упражнение 5-4. Друга интересна възможност е курсорът

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

Например,
myScreen.find( "this" );
Реализирайте find( char* ).

Помощни функции


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

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

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

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

Най-често те се декларират като лични. checkRange(),

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

такива функции. row() връща реда, на който курсорът е

позициониран:


inline Screen::row()

{ // return current row

int pos = cursor - screen + 1;

return (pos+width-1)/width;

}

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


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

189.
col() връща колоната, в която се намира курсорът:
inline Screen::col()

{ // return current column

int pos = cursor - screen + 1;

return ((pos+width-1) % width)+1;

}
remainingSpase() връща броя интервали, оставащи на екрана без

да се брои текущата позиция:


inline Screen::remainingSpase()

{ // current position is no longer remaining

int sz = width*heigth;

return( screen + sz - cursor - 1 );

};
stats() показва информацията, връщана от трите предходни

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

тук части от програми.
void inline Screen::stats()

{

cout << "row: " << row() << "\t";



cout << "col: " << col() << "\t";

cout << "rm: " << remainingSpace() << "\n";

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

член функциите, реализирани до тук.


#include "Screen.C"

#include


main()

{ // exercise cursor movements

Screen x(3,3);

int sz = x.getHeigth() * x.getWidth();


cout << "Screen Object ( "

<< x.getHeight() << ", " << x.Width()

<< " ) ( size: " << sz << " )\n\n";
x.home();

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

{ // ``<= '' in order to wrapround

x.stats();

x.forward();

}

return 0;



}

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


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

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

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


Screen Object ( 3, 3 ) ( size: 9 )
row: 1 col: 1 rm: 8

row: 1 col: 2 rm: 7

row: 1 col: 3 rm: 6

row: 2 col: 1 rm: 5

row: 2 col: 2 rm: 4

row: 2 col: 3 rm: 3

row: 3 col: 1 rm: 2

row: 3 col: 2 rm: 1

row: 3 col: 3 rm: 0

row: 1 col: 1 rm: 8

Функции за достъп
Скриването на информацията капсулира вътрешното

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

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

така важно е да бъде защитено вътрешното състояние на обекта

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

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

обект за запис. Ако се получи грешка, областта за търсене се

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

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

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

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

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

Ето две функции set(), които позволяват на потребителя да пише

на екрана:


void Screen::set( char *s )

{ // write string begining at screen element


int space = remainingSpace();

int len = strlen( s );

if ( space < len )

{

cerr << "Screen: warning: truncaton: "



<< "space: " << space

<< "string length: " << len << "\n";

len = space;

}
for ( int i = 0; i < len; ++i )

*cursor++ = *s++;

}

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


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

191.
void Screen::set( char ch )

{

if ( ch == '\0' )



cerr << "Screen: warning: "

<< "null character (ignored).\n";

else *cursor = ch;

}
При реализацията на нашия клас Screen сме възприели eдно

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

е причината, поради която set() не разрешава нулевия символ да

бъде изписан на екрана.

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

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

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

isEqual().

isEqual(char ch) връща истина ако ch е еднакъв със

символа, намиращ се на текущата позиция на курсора. Ето

реализацията й:
class Screen

{

public:



isEqual( char ch ) { return (ch == *cursor ); }

// ...


}
isEqual(char* s) връща истина ако масивът от символи,

започващ от текущата позиция на курсора е еднакъв с s.


#include
Screen::isEqual( char *s )

{ // yes? return 1; otherwise, 0


int len = strlen( s );

if ( remainingSpace() < len )

return 0;
char *p = cursor;

while ( len-- > 0 )

if ( *p++ != *s++ )

return 0;


return 1;

}
isEqual(Screen&) връша истина ако два обекта Screen са

еднакви; т.е. височината, ширината и съдържанието на двата

екрана трябва да бъдат еднакви.

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

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

192.
Screen::isEqual( Screen& s )

{

// first, are they phisically unequal?



if ( wigth != s.width || heigth != s.heigth )

return 0;


// do both share the same screen?

char *p = screen;

char *q = s.screen;

if p == q return 1;


// be careful not to walk off the Screens

while ( *p && *p++ == *q++ );


if ( *p ) // loop broke on not equal

return 0;


return 1;

}
Упражнение 5-5. Сравнете реализацията без член функция

в Раздел 5.2. (стр. 182) с тази реализация. Защо двете

изглеждат различно? Еквивалентни ли са?

Член функции const
При опит за модифициране на обект comst, който не е

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

компилация. Например,
const char blank = ' ';

blank = '\0'; // error


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

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

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

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

небезопасни член функции. Например,
const Screen blankScreen;

blankScreen.display(); // safe

blankscreen.set( '*' ); // unsafe
Проектантът на класа отбелязва кои член функции са безопасни

чрез думата const. Например,

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

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

193.
class Screen

{

public:



char get() const { return *cursor; }

// ...


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

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

Ключовата дума const се поставя между списъка от

аргументи и тялото на член функцията. Една член функция const,

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

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

Например,
class Screen

{

public:



isEqual( char ch ) const;

// ...


private:

char *cursor;

// ...

};
Screen::isEqual( char ch ) const



{

return( ch == *cursor );

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

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

Screen, например,
class Screen

{

public:



void ok(char ch) const { *cursor = ch; }

void error(char *pch) const { cursor = pch; }

// ...

private:


char *cursor;

// ...


};
ok() правилно е определена като const, понеже не променя

стойността на cursor, а по-скоро променя стойността на адреса

на обекта cursor. error(), обаче, изменя фактическата

стойност на cursor и следователно не може да бъде дефинирана

като член функция const. Като резултати от декларациите имаме

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

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

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

194.
error: assignment to member Screen::cursor of

const class Screen


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

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

сигнатура. Например,
class Screen

{

public:



char get( int x, int y );

char get( int x, int y ) const;

// ...

};
В този случай константността на обекта от тип клас определя



коя от функциите да бъде извикана:
const Screen cs;

Screen s;


main()

{

char ch = cs.get(0,0); // const member



ch = s.get(0,0); // nonconst member

}
Конструкторите и деструкторите са изключения и не е

необходимо да бъдат декларирани като const за да бъдат

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

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

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

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

Screen, които могат да бъдат дефинирани като const.

5.4. Неявен указател this
Може да се каже, че е налице определена липса на

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

класа Screen. Обработките на един екран се грижат за

извършване на редица действия: почистване, местене, писане,

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

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

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

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

195.
Screen myScreen( 3, 3 ), bufScreen;
main()

{

myScreen.clear();



myScreen.move(2,2);

myScreen.set('*');

myScreen.display();
bufScreen.reSize(5,5);

bufScreen.display();

}
Необходимостта да се пишат нови оператори за всяко действие,

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

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

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


main()

{

myScreen.clear().move(2,2).set('*').display();



bufScreen.reSize(5,5).display();

}
Този раздел илюстрира как може да бъде реализиран този

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

функцииите на класа сами по себе си?

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

член данните на класа. myScreen има свои width, heigth,

cursor и screen; bufScreen има собствен отделен набор. Но

както myScreen така и bufScreen, обаче, ще извикват едно и

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

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

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

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

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

копията на функциите, които всеки обект ще

дефинира.
2. Ако съществува само един представител на дадена

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

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

обработвани в член функциите? Как, например,

cursor, обработвана от move() ще се свърже с

cursor, принадлежащ на myScreen или на bufScreen?


Отговор на това дава указателя this. Всяка член

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

член функцията на Screen, например, указателят this е от тип

Screen*; в член функцията IntList той е от тип IntList*.

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

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

196.
Указателят this съдържа адреса на обекта от тип клас,

за който член функцията е извикана. По този начин cursor,

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

myScreen и bufScreen.

Един начин за разбиране на този указател e да се

разгледа как един компилатор, например, AT&T езиковата

система, го реализира. Тя го организира в следните две стъпки:
1. Транслира член функциите на класа. Всяка член

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

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

указателя this. Например,


home__Screen( Screen *this )

{

this->cursor = this->screen;



}
2. Транслира всяко извикване на член на клас.

Например,


myScreen.home()
се превежда в
home__Screen( &myScreen );
Програмистът може да използува указателя this и явно.

Например, правилно е, въпреки че е безмислено, да се пише

следното:
inline void Screen::home()

{

this->cursor = this->screen;



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

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

Използуване на указтеля this
Указателят this е ключът за реализиране на синтаксиса

с конкатинация за класа Screen. Член операторите за селекция

("." и "->") са ляво асоциативни бинарни оператори. Редът за

изпълнение е от ляво надясно. myScreen.clear() се извиква

първо. За да бъде извикана след това move(), clear() трябва да

върне обект от класа Screen. За правилното извикване на

move(), clear() трябва да върне обект от тип myScreen. Всяка

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

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

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

clear():

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


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

197.
Screen& Screen::clear( char bkground )

{ // reset the cursor and clear the screen

char *p = cursor = screen;
while ( *p )

*p++ = bkground;


return *this; // return invoking object

}
Към дефинициите на move(), home(), функциите set() и четирите

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

оператора return *this и да бъде променен типа им за връщане

от void на Screen&.

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

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

функции.
Screen&

Screen::lineX( int row, int col, int len, char ch)

{ /* provide straight line in row beginning at col

* of length len using character ch */
move( row, col );

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

set( ch ).forward();
return *this;

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

линия с някяква дължина надолу в определена колона:
Screen& lineY( Screen& s, int row, int col,

int len, char ch )


{ // provide vertical line at col

s.move( x, y );

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

s.set(ch).down();


return s;

}
Упражнение 5-7. Една член функция би отстранила

лексикалната сложност на оператора за избор на член чрез

механизма на указателя this. За да видите това по-ясно

напишете отново lineY() като член функция на Screen.
Член функцията на Screen display() може да бъде

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

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

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

198.
Screen& Screen::display()

{

char *p;



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

{ // for each row

cout << "\n";

int offset = width * i; // row postion

for ( int j = 0; j < width; ++j )

{ // for each column, write element

p = screen + offset + j;

cout.put( *p );

}

}

return *this;



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

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

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

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

заменят извикания обект от тип Screen с този нов обект.
*this = *ps;
където ps сочи към новият обект от тип Screen. Една реализация

на reSize() има вида:


Screen&

Screen::reSize( int h, int w, char bkground )

{ // reSize a screen to heigth h and width w
Screen *ps = new Screen( h, w, bkground );

char *pNew = ps->screen;


// Is this screen currently allocated?

// If so, copy old screen contents to new

if ( screen )

{

char *pOld = screen;



while ( *pOld && *pNew )

*pNew++ = *pOld++;

delete screen;

}
*this = *ps; // replace Screen object

return *this;

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

достъп до указателя this. Например,

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


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

199.
class DList

{ // doulby-linked list

public:

void append( DList* );



// ...

private:


DList *prior, *next;

};
void DList::append( DList *ptr )

{

ptr->next = next;



ptr->prior = this;

next->prior = ptr;

next = ptr;

}
Ето една малка програма, която демонстрира някои от

член функциите на Screen, дефинирани в този и предходните

раздели.
#include "Screen.h"

main()

{

Screen x(3,3);



Screen y(3,3);
// if equal, return 1

cout << "isEqual( x, y ): (>1<) "



<< x.isEqual(y) << "\n";
y.reSize( 6, 6 ); // double it
cout << "isEqual( x, y ): (>0<) "

<< x.isEqual(y) << "\n";
// draw a line on the Y axix

lineY(y,1,1,6,'*'); lineY(y,1,6,6,'*');


// draw a line on the X axix

y.lineX(1,2,4,'*').lineX(6,2,4,'*').move(3,3);


// write to screen and display

y.set("hi").lineX(4,3,2,'^').display();


// x and y equal in size, but not content

x.reSize( 6, 6 );

cout << "\n\nisEqual( x, y ): (>0<) "

<< x.Equal(y) << "\n";

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


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

200.
// now, both are equal

x.copy( y );

cout << "isEqual( x, y ): (>1<) "

<< x.isEqual(y) << "\n";
return 0;

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

получава следния резултат:
isEqual( x, y ): (>1<) 1

isEqual( x, y ): (>0<) 0


******

*####*


*#hi#*

*#^^#*


*####*

******
isEqual( x, y ): (>0<) 0

isEqual( x, y ): (>1<) 1
където стойностите, заградени със скоби са очакваните

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

стойности, генерирани от обръщението към isEqual().

5.5. Приятели на клас


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

информация са доста ограничаващи. Механизмът за приятелство

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

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

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

Операторите за вход и изход от iostream (">>","<<")

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

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

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

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


Screen myScreen;

cout << myScreen;

cout << "myScreen: " << myScreen << "\n";
Както операторът за вход, така и операторът за изход

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

обекта от iostream, с който са оперирали. Това позволява

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

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

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


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

201.
((( (cout << "myScreen: ") << myScreen ) << "\n")
Всеки подизраз, заграден в скоби, връща обекта cout от

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


cout << myScreen
трябва да бъде реализиран като
ostream &operator<<( ostream&, Screen& );
Тази реализация, обаче, предотвратява дефинирането на

тези оператор функции като член функции на Screen.

Ето декларацията на оператора за изход като член

функция на Screen:


class Screen

{

public:



ostream &operator<<( ostream& );

// ...


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

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

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

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

слединя вид:
myScreen << cout;
Би било объркващо както за програмиста, така и за

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

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

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

непубличните членове на класа Screen - много от които се

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

приятелството.

Приятел се нарича всеки нечлен на класа, на който е

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

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

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

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

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

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

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

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

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

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

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

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

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

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

202.
class Screen

{

friend iostream&



operator>>( iostream&, Screen& );

friend iostream&

operator<<( iostream&, Screen& );

public:


// ... rest of the Screen class

};
Как може за бъде написан оператора за изход на Screen?

Необходими са три от член данните на Screen - heigth, width и

адреса на фактическият масив от символи screen. За упростяване

курсорът се връща в позиция home когато се чете обектът

Screen. Форматът на изхода за обект Screen има вида:



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




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

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