Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекцій по С.doc
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
8 Мб
Скачать
☆

5.6 Поточний об'єкт this, указники на члени класу і класні функції, порівняння з указниками на (позакласні) функції, указники на статичні члени класу

Поточний об'єкт this, указники на члени класу і класні функції, порівняння з указниками на (позакласні) функції, указники на статичні члени класу

Тепер подивимося, як методи дістаються до своїх атрибутів. Наприклад, визначимо

два вікна

const Screen w(4,3,"aaaabbbbcccc");

Screen u(w);

кожен з них змінить значення свого курсору, так що присвоєння

_cursor = _width*i + j;

застосовується до свого атрибуту:

w._cursor == 11

а

u._cursor == 14.

Як бачимо, метод, активізований об'єктом, повинен знати, який саме об'єкт,

його активізував. Для цього існує указник поточного об'єкту this , його тип

Screen* . Отже повний текст будь-якого методу, наприклад, методу move мав би

наступний вигляд

void Screen::move(int i, int j) const

{

if ((i>=this->_height) || (j>=this->_width))

this->_cursor=0;

else

this->_cursor = this->_width*i + j;

};

або для копіювального конструктора

Screen::Screen(const Screen& v)

{

this->_filler = v._filler;

this->_height = v._height;

this->_width = v._width;

this->_wContent = new char [_height*_width+1];

strcpy(this->_wContent,v._wContent);

this->_cursor=v._cursor;

}

Ясно, що в усіх цих випадках діє правило замовчування: “безпритульні” атрибути

і методи приписуються поточному об'єкту. Але легко уявити собі ситуацію, коли

явне посилання на поточний об'єкт стає необхідним. Як приклад розглянемо копіювання

параметру до поточного об'єкту

void Screen::copy(const Screen& v)

{

if (this != &v)

{

delete []_wContent;

_height = v._height;

_width = v._width;

_wContent = new char [_height*_width+1];

strcpy(_wContent,v._wContent);

_cursor=v._cursor;

}

};

Копіюванню передує перевірка на збіг об'єкту, що копіюється, з цільовим об'єктом.

Якщо такої перевірки не зробити, то доведеться виконувати зайві і , власне,

не цілком коректні дії.

Інше важливе застосування указника на поточний об'єкт виникає при необхідності

повернення поточного об'єкту результатом функції. Найчастіше ця ситуація виникає

при довизначенні операцій. Вона буде розглянута в наступному розділі. Ось ще

один типовий приклад. Розглянемо таку послідовність дій

Screen myScreen(3,3,””);

myScreen.clear();

myScreen.move(1,1);

myScreen.set(’*’);

myScreen.move(2,2);

myScreen.set(’*’);

myScreen.move(3,3);

myScreen.set(’*’);

myScreen.show();

Існує простий спосіб, як записати цю послідовність інструкцій у вигляді одного

виразу

myScreen.clear().move(1,1).set(’*’).

move(2,2).set(’*’).

move(3,3).set(’*’).show();

Для цього функції повинні повертати значенням свій поточний об'єкт

Screen& Screen::home()

{

_cursor=0;

return *this;

};

Screen& Screen::move(int i, int j)

{

if ((i>=_height) || (j>=_width))

_cursor=0;

else

_cursor = _width*i + j;

return *this;

};

Screen& Screen::move()

{

if ((++_cursor)>=_width*_height) _cursor=0;

return *this;

};

Screen& Screen::set(char a)

{

*(_wContent + _cursor)=a;

return *this;

}

Screen& Screen::clear()

{

for (int i=0; i<_height*_width; i++)

*(_wContent+i)= _filler;

_cursor=0;

return *this;

}

та відповідно модифікувавши інтерфейс

class Screen

{

public:

Screen(int, int, char*);

Screen(const Screen&);

~Screen();

void home() const;

void move(int, int) const;

void move() const;

char get() const;

void show() const;

void copy(const Screen&);

Screen& home();

Screen& move(int, int);

Screen& move();

Screen& set(char);

Screen& clear();

private:

static const int maxHeight;

static const int maxWidth;

int _height;

int _width;

char *_wContent;

mutable int _cursor;

char _filler;

};

Зверніть увагу на те, що ново визначені функції не константні. В принципі

їх можна визначати константними, якщо добавити кваліфікатор const двічі

const Screen& home() const ;

інакше значення об'єкту можна було б змінити, помістивши виклик в позицію,

яка вимагає лівостороннього значення.

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

у об'єктах.

Ось простий клас калькулятора з суматором і двома регістрами

class Calc

{

friend void utilityCalc();

unsigned int a;

unsigned int x;

unsigned int y;

public:

unsigned int add();

unsigned int mult();

unsigned int div();

unsigned int subt();

}

Арифметичні операції виконуються на суматорі, наприклад,

unsigned int Calc::add() { return a=a+x;}

Якщо ми візьмемо об'єкт c класу Calc , то атрибут c.a матиме тип unsigned

int . Це значить, що будь-якій змінній i типу unsigned int можна присвоїти

значення c.a . Спроба присвоїти змінній i просто значення a ясна річ не коректна.

Так само як некоректна спроба помістити на a указник типу unsigned int* . Дійсно

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

void utilityCalc()

{

Calc c;

unsigned int i, *ip;

i=c.a; //OK

i=a; //Error

ip=&a; //Error

}

Але все ж таки можна домовитися позначати певний атрибут в об'єкті за допомогою

указника, надавши указнику іншого типу, наприклад, anyReg

unsigned int Calc::*anyReg;

визначено як указник атрибуту типу unsigned int в об'єкті типу Calc .

Відповідно надамо йому значення

anyReg = &Calc::a;

сказавши, що указник показуватиме на атрибут у об'єктах Calc.

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

typedef unsigned int Calc::*Register;

Register r = &Calc::a;

Щоб добратися до самого атрибуту об'єкт все ж таки потрібен

Calc c;

c.*anyReg = 1;

присвоєння відбудеться атрибуту, на який вказує anyReg , тобто c.a .

Ось трохи складніший приклад

void CycleShiftLeft

(unsigned int Calc::*regPtr, Calc c[], int n)

{

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

{

unsigned int mask = 0x80000000;

mask = (c[i].*regPtr & mask) << 31;

(c[i].*regPtr<<=1)|=mask;

}

}

В масиві c відбувається циклічний зсув атрибуту, позначеного указником regPtr

.

Схожі проблеми виникають при спробі непрямого доступу до класних функцій.

Те що об'єкти класу розділяють функції не спрощує проблеми, кожна функція знає

свій об'єкт, який її активізував. Для цього в ній присутній указник this .

Ось, наприклад, указник op на функцію без параметра з класу Calc

unsigned int (Calc::*op) ();

Доповнимо клас Calc функцією exe , що виконує довільну операцію калькулятора

class Calc

{

friend void utilityCalc();

unsigned int a;

unsigned int x;

unsigned int y;

public:

unsigned int add();

unsigned int mult();

unsigned int div();

unsigned int subt();

unsigned int exe(unsigned int (Calc::*) ());

}

її реалізація

unsigned int Calc::exe(unsigned int (Calc::*op) ())

{

return (this->*op)();

}

А ось приклад виклику

void utilityCalc()

{

Calc b;

b.x=10; b.a=1;

cout<<b.exe(&Calc::add)<<endl;

}

Ще один приклад показує особливості посилань на константні і неконстантні

функції

typedef void (Screen::*Action) () const;

void doAction1(Screen& s, Action act, int n)

{

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

(s.*act)();

}

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

void doAction2(Screen& s, AnotherAction act, int n)

{

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

(s.*act)();

}

Тут визначено дві позакласні функції для повторення довільної дії з класу

Screen. А ось приклад їх виклику

Screen v(2,4,"11223344");

doAction1(v, Screen::move, 7);

doAction1(v, Screen::back, 5);

doAction2(v, Screen::move, 7);

doAction2(v, Screen::back, 2);

Пересвідчіться, що doAction1 викликає функцію з сигнатурою

void move() const;

а doAction1

Screen& move();

Також можна влаштувати масив з указників на класні функції

Action menu[] =

{

Screen::home,

Screen::move,

Screen::back,

Screen::show

};

Указники на статичні атрибути і статичні методи нічим не відрізняються від

звичайних указників даних і функцій, оскільки статичні атрибути знаходяться

поза об'єктами, а статичні функції не знають поточного об'єкту.

class Account

{

friend class Bank;

static double interestRate;

public:

static void raiseInterest(double);

static double getInterest();

};

class Bank

{

double * RatePtr;

void (* raisePtr)(double);

double (* getPtr)();

public:

Bank();

};

Bank::Bank()

{

RatePtr = &Account::interestRate;

raisePtr = &Account::raiseInterest;

getPtr = &Account::getInterest;

}