Покажчики на члени класу

 

Покажчики на члени класу вказують на нестатичні поля або методи (включаючи віртуальні) будь-якого об’єкта класу.

 

struct Point

{

int x, y;

void Set(int x0, int y0){x = x0, y = y0;}

};

 

void main()

{

Point a, *pa = &a;

// pint - покажчик на будь-який int член класу Point

int Point::*pint = &Point::x;

// pmf2i - покажчик на будь-яку функцію-член класу Point,

// яка приймає два аргументи типу int і повертає void

void (Point::*pmf2i)(int,int) = &Point::Set;

 

++(a.*pint); // інкремент х

++(pa->*pint); // знову інкремент х

(a.*pmf2i)(1,1); // виклик Set()

(pa->*pmf2i)(2,2); // виклик Set()через pa

}

 

Покажчики на члени класу неявно перетворюються в покажчики на члени похідних класів, якщо це необхідно. Вони не перетворюються неявно в покажчики на void, як звичайні покажчики.

 

 


Лекція 10. Перевантаження операторів

1. Перевантаження операцій

Перевантаження операцій дозволяє розширювати на типи класів значення будь-яких операцій, крім .,.*, ::, sizeof, ? :.

Перевантаження операцій здійснюється шляхом оголошення функції, що складається із ключового слова operator, за яким іде одна зі вбудованих операцій. Перевантаження операцій не може використовувати аргументи за замовчуванням.

2. Унарні оператори

Унарні оператори можуть бути оголошені як нестатичні функції-члени, що не мають аргументів. Тобто @x інтерпретується як x.operator@() для будь-якого оператора @. Унарні оператори можуть бути оголошені як функції, котрі не є членами якого-небудь класу, які мають один аргумент. Цей аргумент є змінною даного класу або посиланням на таку змінну. У цьому випадку @x інтерпретується як operator@(х) для будь-якого оператора @.

 

class X

{

X operator - () const; // унарний мінус

X operator & () const; // обчислення адреси

X operator ^ () const; // помилка: операція ^ бінарна

};

 

class Y

{

friend Y operator - (const Y&); // унарний мінус

friend Y operator & (const Y&); // обчислення адреси

friend Y operator ^ (const Y&); // помилка: операція ^ бінарна

};

3. Бінарні оператори

Бінарні операції можуть бути оголошені як нестатичні функції, що мають один аргумент, тобто x@y інтерпретується як x.operator@(y) для будь-якого оператора @. Бінарні операції можуть бути оголошені як функції, котрі не є членами якого-небудь класу, що мають два аргументи. Тобто для будь-якої операції @ x@y інтерпретується як operator@(х, y).

 

class X

{

X operator- (const X&)const; //бінарний мінус

X operator & (const X&) const; // побітове І

X operator ! (const X&) const; // помилка: операція ! унарна

};

 

class Y

{

friend Y operator - (const Y&, const Y&);

friend Y operator & (const Y&, const Y&);

friend Y operator ! (const Y&, const Y&); // помилка

};

 

4. Операція виклику функції

Операція виклику функції повинна бути оголошена як нестатична функція-член класу. Операція виклику функції дозволяє користувачеві визначати кількість операндів.

 

class X

{

// ...

public:

X(int a, int b, int c);

// ...

void operator () (int i, int j, int k);

 

};

 

X Xamle(1,2,3); // викликається конструктор

Xample(4,5,6); // викликається операція ()

 

Приклад:

 

// function_call.cpp

class Point

{

public:

Point() { _x = _y = 0; }

Point &operator()( int dx, int dy )

{ _x += dx; _y += dy; return *this; }

private:

int _x, _y;

};

 

int main()

{

Point pt;

pt( 3, 2 );

}

5. Операція присвоювання

Операція присвоювання використовується для присвоювання значення одного об’єкта даного класу іншому. Якщо не визначена користувачем, то виконує присвоювання значень полів одного об’єкта даного класу іншому. Повинна бути визначена, коли клас містить поля, які є покажчиками на динамічно виділену пам’ять. Повинна бути визначена як нестатична функція-член класу.

 

class IntStack

{

int *v, size, top;

// ...

public:

IntStack& operator = (const IntStack& from)

{

if(this != &from) // перевірка на from = from

{

delete[] v;

v = new int[size = from.size];

for(register int i=0;i<size;++i)

v[i] = from.v[i];

top = from.top;

}

return *this;// дозволяє виконувати множинне присвоювання

}

 

};

 

// ...

IntStack a(100), b, c;

// ...

c = b = a; // привласнити значення а змінним b і с

 

Приклад:

 

// assignment.cpp

#include <iostream>

 

using namespace std;

 

class Point

{

public:

Point &operator=( Point & ); // Right side is the argument.

int _x, _y;

};

 

// Define assignment operator.

Point &Point::operator=( Point &ptRHS )

{

_x = ptRHS._x;

_y = ptRHS._y;

 

return *this; // Assignment operator returns left side.

}

 

int main()

{

Point pt1, pt2, pt3;

pt1._x = 10;

pt1._y = 20;

pt3 = pt2 = pt1;

cout<<"pt3.x = "<<pt3._x<<" pt3.y = "<<pt3._y<<endl;

 

}

 

6. Операція індексації

Операція індексації повинна бути визначена як нестатична функція-член класу. Операція індексації найчастіше повертає посилання, що дозволяє використовувати її по обидва боки операції присвоювання.

 

class String

{

char* s;

public:

String(char);

String(const char*);

char& operator [](int pos){return s[pos];}

// ...

};

 

String ball = "mitten";

ball[0] = 'k'; // char& допускає присвоювання

 

Приклад:

 

// subscripting.cpp

// compile with: /EHsc

#include <iostream>

 

using namespace std;

class IntVector

{

public:

IntVector( int cElements );

~IntVector() { delete _iElements; }

int& operator[]( int nSubscript );

private:

int *_iElements;

int _iUpperBound;

};

 

// Construct an IntVector.

IntVector::IntVector( int cElements )

{

_iElements = new int[cElements];

_iUpperBound = cElements;

}

 

// Subscript operator for IntVector.

int& IntVector::operator[]( int nSubscript )

{

static int iErr = -1;

 

if( nSubscript >= 0 && nSubscript < _iUpperBound )

return _iElements[nSubscript];

else

{

clog << "Array bounds violation." << endl;

return iErr;

}

}

 

// Test the IntVector class.

int main()

{

IntVector v( 10 );

 

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

v[i] = i;

 

v[3] = v[9];

 

for( i = 0; i <= 10; ++i )

cout << "Element: [" << i << "] = " << v[i]

<< endl;

 

return v[0];

}

 

 

7. Операція доступу до члена класу

Операція доступу до члена класу для x->m інтерпретується як (x.operator->()) -> m. Це унарна операція і х – це об’єкт класу, а не покажчик на нього. Операція доступу до члена класу повинна повертати покажчик на об’єкт класу, або об’єкт класу, або посилання на об’єкт класу, для якого ця операція визначена, оскільки оригінальне значення операції -> не губиться, а тільки затримується. Операція доступу до члена класу повинна бути визначена як нестатична функція-член класу.

 

 


Лекція 11. Перевантаження операторів (продовження)

1. Операції інкременту і декременту

Операції інкременту та декременту можуть бути перевантажені як для префіксної, так і для постфіксної форм запису.

Префіксна: оголошується як операція-член класу, що не має аргументів, або як дружня операція, яка має один аргумент, що являє собою посилання на об’єкт даного класу.

Постфіксна: оголошується як операція-член класу, яка має один аргумент типу int, або як дружня операція, що має два аргументи: посилання на об’єкт даного класу й аргумент типу int.

 

class X

{

// ...

public:

X& operator++(); // префіксна форма

X operator++(int); // постфіксна форма

};

 

class Y

{

// ...

public:

friend Y& operator—( Y&); // префіксна форма

friend Y operator—(Y&, int); // постфіксна форма

};

 

Приклад:

 

#include <iostream>

 

using namespace std;

 

// increment_and_decrement.cpp

class Point

{

public:

// Declare prefix and postfix increment operators.

Point& operator++(); // Prefix increment operator.

Point operator++(int); // Postfix increment operator.

 

// Declare prefix and postfix decrement operators.

Point& operator--(); // Prefix decrement operator.

Point operator--(int); // Postfix decrement operator.

 

// Define default constructor.

Point() { _x = _y = 0; }

 

// Define accessor functions.

int x() { return _x; }

int y() { return _y; }

private:

int _x, _y;

};

 

// Define prefix increment operator.

Point& Point::operator++()

{

_x++;

_y++;

return *this;

}

 

// Define postfix increment operator.

Point Point::operator++(int)

{

Point temp = *this;

++*this;

return temp;

}

 

// Define prefix decrement operator.

Point& Point::operator--()

{

_x--;

_y--;

return *this;

}

 

// Define postfix decrement operator.

Point Point::operator--(int)

{

Point temp = *this;

--*this;

return temp;

}

int main()

{

Point p;

cout<<"Before increment:n"<<p.x()<<'t'<<p.y()<<'n';

++p;

cout<<"After increment:n"<<p.x()<<'t'<<p.y()<<'n';

 

return 0;

}

2. Операції new і delete

Операції new і delete надають класу механізм керування власною пам’яттю. Вони повинні мати тип значення, що повертається, void* для new і void для delete. Операції new і delete вимагають параметр типу size_t, визначений у стандартному файлі заголовка <stddef.h>.

 

#include <stddef.h>

class Thing

{

// ...

enum {block = 20};

static Thing* freeList;

public:

// ...

void* operator new (size_t);

void operator delete(void*, size_t);

};

 

void* Thing::operator new(size_t size)

{

Thing* p;

if(!freeList)

{

freeList = p = (Thing*) new char [size * block];

while(p != &freeList[block - 1])

p -> next = p + 1, ++p;

p -> next = 0;

}

p = freeList, freeList = freeList -> next;

return p;

}

 

void Thing::operator delete(void* p, size_t)

{

((Thing*)p) -> next = freeList;

freeList = (Thing*) p;

}

 

Якщо операції new і delete визначені для класу, то вони не можуть бути викликані при виділенні й звільненні пам’яті для масивів об’єктів даного класу.

Операція new може бути перевантажена, щоб приймати додаткові аргументи. Передані аргументи, обмежені дужками, розміщуються при виклику після ключового слова new.

 

class Thing

{

// ...

public:

// ...

/* додати додатковий аргумент buf, що буде використовуватися для виділення пам’яті для Thing за зазначеною адресою. */

void* operator new (size_t size, void* buf);

};

 

// ...

void* Thing::new(size_t size, void* buf)

{

// розмістити Thing за адресою buf

}

 

// ...

char buffer[1000];

Thing* p = new (buffer) Thing;

 

Перевантажена операція new з додатковими аргументами приховує глобальну операцію ::operator new. Тепер при звертанні до new зі звичайним синтаксисом відбудеться помилка.

 

Thing* p = new Thing; // помилка: відсутній аргумент

 

Щоб знову використовувати звичайний синтаксис оператора new, для класу повинна бути передбачена функція-член new, що має тільки один аргумент – size_t size. Вона явно викликає глобальну операцію ::operator new.

 

class Thing

{

// ...

void* operator new (size_t size, void* buf);

void* operator new (size_t size)

{

return ::operator new(size);

}

};

 

3. Операції перетворення

Операції перетворення надають механізм перетворення типів class у вбудовані типи або визначені типи class. Мають форму operator type() {/* ... */}, тобто не мають ні явних аргументів, ні значення, що повертається, отже, не можуть бути перевантажені. Використовуються як доповнення до перетворень в арифметичних виразах, ініціалізаціях, аргументах функцій і значеннях, що повертаються.

 

class String

{

char* s;

public:

// ...

operator const char* () const { return s;}

};

 

void f (const char* s) {/* ... */}

String s;

// ...

f(s); // перетворення: s.operator const char*()

 

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

 

class BitString{/* ... */ operator String();};

BitString B;

// ...

f(B); // помилка: перетворення не виконується B.operator String().operator const char*()

 

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

 

Приклад:

 

#include <iostream>

 

using namespace std;

 

// spec_conversion_functions.cpp

class Money

{

public:

Money():_amount(0.0){};

void setAmount(double amount)

{

_amount = amount;

}

operator double() { return _amount; }

private:

double _amount;

};

 

int main()

{

Money Account;

Account.setAmount(125.25);

double CashOnHand = Account;

cout << CashOnHand<<'t'<< (double)Account << endl;

}

 

4. Дружні функції

Дружні функції – це функції, які мають доступ до закритої частини класу, членами якого вони не є, і захищеної частини класу, від якого вони не породжені.

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

 

class Person

{

int secret; // закрита частина класу

public:

// ...

friend void Spose(Person&) // не член класу

};

 

// ...

void Spose(Person& p)

{

++p.secret; // має доступ до закритої частини класу

}

 

Дружні функції можуть бути членами іншого класу.

class Person

{

// ...

friend void Family::Sibling(); // дружня функція

};

 

Дружні функції можуть бути оголошені для всього класу.

 

class Person

{

// ...

friend class Family; // всі функції-члени класу Family є дружніми

};

 

Дружні функції не транзитивні, тобто друзі друзів класу не є друзями даного класу.

 

class Family

{

friend class DistantRelative;

// ...

};

 

class DistantRelative: public Family

{

void Pry(Person& p)

{

++p.secret; // помилка: DistantRelative не є дружнім класу

// Person, хоча він і породжений від дружнього йому класу

}

};

 

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

 

Приклад:

 

// friend_functions.cpp

#include <iostream>

 

using namespace std;

 

class Point

{

friend void ChangePrivate( Point & );

public:

Point( void ) : m_i(0) {}

void PrintPrivate( void ){cout << m_i << endl; }

 

private:

int m_i;

};

 

void ChangePrivate ( Point &i ) { i.m_i++; }

 

int main()

{

Point sPoint;

sPoint.PrintPrivate();

ChangePrivate(sPoint);

sPoint.PrintPrivate();

}

 

 


Лекція 12. Успадкування

1. Похідні класи

Похідні класи надають можливості для створення класу, що є варіацією класу, визначеного раніше. Похідні класи мають доступ до відкритої й захищеної частини базового класу.