Помогни ни да направим Uroci.net по - богат! Добави урок

C++ част.6 (Класове)

C++ » C++
fix3d   трудност:    видян: 44646



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

Указателите, и особено указателите към функции, предлагат един полезен начин за обобщаване на програмите. Потребителите на класа Screen, например, желаят да напишат функция “повторител”, която да изпълнява някаква дефинирана от тях операция n пъти. Една възможна реализация има вида:

Screen &repeat( char op, int times )

{

switch( op )

{

case DOWN: // ... invoke

Screen::down() break;

case UP: // ... invoke

Sscreen::up() break;

// ...

} }

Въпреки, че тази функция работи, тя има няколко основни недостатъка. Единият проблем се състои в това, че тя много явно се осланя на член функциите на Screen. Винаги когато бъде добавяна или отстранявана някаква член функция, repeat() трябва да бъде изменяна. Вторият проблем е свързан с размера й. Поради наличието на проверка за всяка член функция пълният списък в repeat() е огромен и изглежда много сложен. Една алтернативна и много по-обща реализация заменя op с аргумент от тип указател към член функция на Screen. repeat() не е необходимо повече да определя предвижданите оператори. Целият оператор switch може да бъде отстранен. Дефинирането и използуването на указатели към членове на класове е тема на следващия подраздел.

Типът член на клас

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

int (*pfi)();

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

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

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

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

HeigthIs() { return HEIGTH; }

WidthIs() { return WIDTH; }

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

pfi = HeigthIs; pfi = WidthIs;

Присвояването на една от член функциите на класа Screen getHeigth() или getWidth(), обаче, се явява нарушаване на типовете и ще предизвика грешка по влеме на компилация:

// illegal assignment:

type violation pfi = Screen::getHeigth;

Защо се получава нарушаване на типовете?

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

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

2. Типът на данните за връщане.

3. Типът на класа, чийто член е функцията.

Декларацията на указател към член функция изисква един разширен синтаксис, при който се взема предвид и типа на класа. Същото се отнася и за поддържане на указатели към член данни на клас. Да разгледаме типа на члена на класа Screen heigth. Пълният тип на Screen::heigth e “член short на класа Screen”.

Следователно, пълният тип на указател към Screen::heigth е “указател към члена short на класа Screen”. Указателят this е дефиниран така:

short Screen::

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

short Screen::*ps_Screen;

ps_Screen може да бъде инициализиран с адреса на heigth така:

short Screen::*ps_Screen = &Screen::heigth;

По подобен начин на него може да бъде присвоен адреса на width:

ps_Screen = &Screen::width;

ps_Screen може да бъде инициализиран както с width така и с heigth тъй като и двете са член данни на класа Screen от типа short. При опит да се вземе адреса на непубличен член на класа някъде в програмата без съответните права на достъп до класа се генерира грешка по време на компилация.

Упражнение 6-13. Какъв е типа на членовете screen и cursor на класа Screen?

Упражнение 6-14. Дефинирайте, инициализирайте и дайте стойност на един указател към членовете screen и cursor на класа Screen.

Указател към член функция се дефинира като се определи нейния тип за връщане, сигнатурата и класа. Например, указател към членовете getHeigth() и getWidth() се дефинира така:

int (Screen::*)()

Т.е., това е указател към член функция на Screen, която няма аргументи и типът й за връщане е int. Указател към член функция може да бъде инициализиран и да му бъде присвоена стойност по следния начин:

// all pointers to class member may be assigned 0

int

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

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

pmf1 = pmf2;

pmf2 = Screen::getWidth;

Използуването на typedef може да направи синтаксиса на дефинирането на указатели към функции по-лесен за четене. Например, следният typedef дефинира Action като алтернативно име на тип за Screen& (Screen::*)() т.е. за указател към член функция на Screen, която не получава аргументи и връща псевдоним на обект от класа Screen.

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

Action deFault = Screen:home;

Action next = Screen::forward;

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

Указатели към членове могат да бъдат декларирани като аргументи на функции; може да бъде зададен и инициализатор на аргумент по подразбиране. Например,

action( Screen&, Screen& (Screen::*)() );

action() е декларирана да получава два аргумента:

1. Псевдоним на обект от класа Screen.

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

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

Screen myScreen;

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

Action deFault = Screen::home;

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

ff();

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

action( myScreen, deFault );

action( myScreen, Screen::bottom );

}

Упражнение 6-16. Ууказатели към членове също могат да бъдат декларирани като член данни на клас. Изменете дефиницията на класа Screen така, че да съдържа указател към член функция на Screen от типа на home() и bottom().

Упражнение 6-17. Изменете съществуващия конструктор на Screen (или задайте нов конструктор) така, че да получава указтел към член аргумент на Screen от типа, описан в предното упражнение. Задайте аргумент по подразбиране. Нека някоя функция за достъп да позволява на потребителя да задава стойност на този член.

Използуване на указател към член на клас

Указателите към членове на даден клас трябва винаги да бъдат достъпни през един специфичен обект на класа. Това може да бъде направено като се използуват двата указателя към операторите за избор на член (“.*” за обекти класове и псевдоними и “->” за указатели към обекти класове). Например, указатели към член фуннкции могат да бъдат извикани така:

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

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

Screen myScreen, *BufScreen;

// direct invocation of member function

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

BufScreen->copy( myScreen );

//equivalent invocation through pointers to members

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

(BufScreen->*pmfS) (myScreen);

Обръщенията (myScreen.*pmfi)() (BufScreen->*pmfi)() изискват скоби понеже приоритетът на оператора за обръщение (“()”) е по-висок от приоритета на указателя към оператора за избор на член. Без скобите myScreen.*pmfi() ще бъде интерпретирано като myScreen.*(pmfi()) т.е. като извикване на функцията pmfi() и свързване на връщаната стойност с указателя към оператора за избор на член на обект (“.*”).

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

typedef short Screen::*ps_Screen;

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

ff() { ps_Screen pH = &Screen::heigth;

ps_Screen pW = &Screen::width;

tmpScreen->*pH = myScreen.*pH;

tmpScreen->*pW = myScreen.*pW;

}

Понеже heigth и wigth са лични членове на класа Screen инициализирането на pH и pW в ff() е възможно само ако ff() е декларирана като приятел за класа. Иначе всеки опит на ff() да вземе адресите на тези членове на класа ще бъде отбелязан като грешка по време на компилация.

Ето реализацията на член функцията repeat(), която разгледахме в началото на този раздел:

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

Screen& Screen::repeat( Action op, int times )

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

(this->*op)();

return *this;

}

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

class Screen

{ public:

Screen &

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

// ... }

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

Screen myScreen;

myScreen.repeat();

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

myScreen.repeat( Screen::down, 20 );

Може също да бъде дефинирана и таблица от указатели към членове на клас. В следващия пример Menu е таблица от указатели към тези член функции на класа Screen, които осигуряват движението на курсора. Освен това е дафиниран един изброим тип CursorMovements, свързан с Menu.

Action Menu[] = {

Screen::home,

Screen::forward;

Screem::back,

Screen::up,

Screen::down,

Screen::bottom };

enum CursorMovements { HOME, FORWARD, BACK, UP, DOWN, BOTTOM };

Ще предложим един презаредим представител на move(), който получава аргумент CursorMovements. Ето реализацията:

Screen& Screen::move( CursorMovements cm )

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

return *this;

}

Този представител на move() може да се използува в диалогова програма, в която потребителят избира вида на движението на курсора от меню, показано на екрана.

Упражнение 6-18. Дефинирайте презаредим представител на repeat(), който получава аргумент CursorMovements.

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

Статичните членове на даден клас се оказват вън от синтаксиса на указателите към членовете на класа. Статичните членове принадлежат на класа, а не на представителите му. Декларацията на указател към статичен член на клас изглежда така както тази на указател към некласов член. Съотнасянето на указателя не изсква обект клас. Например, нека разгледаме класа CoOp отново:

class CoOp {

friend compareCost( CoOp&, CoOp* );

public:

CoOp( int, char* );

inline double monthlyMaint();

static void raiseCost( double incr );

static double getCost() { return costPerShare;}

private: static double costPerShare;

int shares;

char *owner;

};

void CoOp::raiseCost(double incr) { costPerShare += incr; }

Типът на &costPerShare е double*; той не е double CoOp::*. Дефиницията на указател към costPerShare изглежда така:

// not double CoOp::*pd double *pd = &CoOp::costPerShare;

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

CoOp unit;

double maint = *pd * unit.shares;

По подобие на това типът на getCost() e double (*)(); а не е double(CoOp::*)(). Дефиницията на указател и индиректното обръщение към getCost() се обработват по същия начин както при некласовите указатели:

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

double maint = pf()*unit.shares;


Страници: «4 5 6 7 8 9 10 »

Сподели урока:



Коментари (1)

odissea на 19.02 2009 в 17:32ч.
добро - но прекалено дълго ... отказах се да го чета до края

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


Калдейта Ком ЕООД - © 2003-. Всички права запазени.
Препоръчваме: IT Новини