C++. Перевантаження операторів -> та ‘,’ (кома). Приклади

Перевантаження операторів -> та ‘ , ‘ (кома). Приклади


Зміст


1. Особливості перевантаження оператора посилання на член об’єкта ->. Загальна форма. Операторна функція operator->()

У C++ оператор доступу до члена об’єкта -> можна перевантажувати. Якщо оператор -> перевантажений, то виклик елемента класу має такий загальний вигляд

obj->item

де

  • obj – об’єкт класу;
  • item – деякий елемент класу (внутрішня змінна, метод). Цей елемент повинен бути членом класу, який є доступний всередині об’єкту.

При перевантаженні слід враховувати наступні особливості:

  • оператор -> вважається унарним;
  • операторна функція operator->() повинна повертати покажчик на об’єкт класу, для якого він визначений;
  • операторна функція operator->() повинна бути членом класу, для якого вона реалізується.

Загальна форма класу, в якому перевантажений оператор ->, наступна

class ClassName
{
    // ...

    // операторна функція
    ClassName* operator->()
    {
        // тіло операторної функції
        // ...

        return this; // повернути покажчик на об'єкт класу
    }
};

 

2. Приклад перевантаження оператору -> в класі, що містить одиночну внутрішню змінну типу double

Оголошується клас Double, що містить внутрішню змінну d типу double. У класі реалізована перевантажена функція operator->(), яка перевантажує оператор ->.

#include <iostream>
using namespace std;

class Double
{
public:
    double d; // внутрішня змінна

    // операторна функція, що перевантажує
    // оператор ->
    Double* operator->()
    {
        return this; // повернути покажчик на клас
    }
};

void main()
{
    // перевантаження оператора доступу за покажчиком ->
    Double D; // екземпляр класу D
    double x;

    D.d = 3.85;

    // виклик операторної функції operator->()
    x = D->d;

    // інший спосіб доступу
    x = D.d; // x = 3.85

    cout << "x = " << x;
}

Результат виконання програми

x = 3.85

 

3. Приклад перевантаження оператору -> для класу, що містить динамічний масив екземплярів класу

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

Задано клас Point, що описує точку на площині. У класі Point реалізовано такі елементи:

  • внутрішні змінні x, y – координати точки;
  • конструктори;
  • методи доступу GetX(), GetY(), SetXY().

Також задано клас Polygon, що реалізує масив точок. У класі оголошуються:

  • внутрішня прихована (private) змінна-покажчик на тип Point. Ця змінна містить покажчик на динамічний масив точок типу Point;
  • внутрішня прихована (private) змінна n, що визначає кількість елементів масиву;
  • конструктор;
  • метод GetN(), який призначений для отримання значення n;
  • метод Add(), який додає нову точку типу Point до масиву. Нова точка отримується як параметр методу;
  • метод Delete(), який видаляє точку з заданої позиції index.

Позиція index є вхідним параметром методу;

  • метод GetPoint(), який повертає значення точки типу Point в заданій позиції index;
  • метод Show(), що відображає на екрані значення масиву точок в класі Polygon;
  • операторна функція operator->(), яка перевантажує оператор ->.


Текст програми типу Console Application, що містить реалізації класів Point та Polygon, наступний

#include <iostream>
using namespace std;

// клас, що описує точку
class Point
{
private:
    double x, y; // внутрішні змінні

public:
    // конструктори класу
    Point()
    {
        x = y = 0;
    }

    Point(double x, double y)
    {
        this->x = x;
        this->y = y;
    }

    // методи доступу
    void SetXY(double x, double y)
    {
        this->x = x;
        this->y = y;
    }

    double GetX(void) { return x; }
    double GetY(void) { return y; }
};

// клас багатокутник
class Polygon
{
private:
    Point * Pt; // масив точок
    int n; // кількість точок

public:
    // конструктор
    Polygon()
    {
        n = 0;
        Pt = NULL;
    }

    // методи доступу
    // зчитати кількість точок
    int GetN(void) { return n; }

    // додати нову точку
    void Add(Point p)
    {
        Point * Pt2;

        // виділити пам'ять на новий масив типу Point - на 1 елемент більше
        Pt2 = new Point[n + 1];

        // скопіювати Pt=>Pt2
        for (int i = 0; i < n;i++)
            Pt2[i].SetXY(Pt[i].GetX(), Pt[i].GetY());

        // додати зайвий елемент
        Pt2[n].SetXY(p.GetX(), p.GetY());

        // знищити пам'ять, виділену під старий масив
        if (n > 0)
            delete[] Pt;

        // присвоїти внутрішній змінній новий масив
        Pt = Pt2;

        n++;
    }

    // видалити точку в позиції pos
    void Delete(int pos)
    {
        if (n < 0) return;
        if (pos > (n - 1)) return;
        if (pos < 0) return;

        // якщо один елемент, то видалити його
        if (n == 1)
        {
            n = 0;
            delete[] Pt;
            return;
        }

        // n>1
        Point* Pt2; // новий масив
        double tx, ty; // додаткові змінні

        Pt2 = new Point[n - 1]; // виділити пам'ять під новий масив - на 1 елемент менше

        // скопіювати Pt в Pt2 в обхід позиції pos
        // до позиції pos
        for (int i = 0; i < pos; i++)
        {
            tx = Pt[i].GetX();
            ty = Pt[i].GetY();
            Pt2[i].SetXY(tx, ty);
        }

        // після позиції pos
        for (int i = pos + 1; i < n; i++)
        {
            tx = Pt[i].GetX();
            ty = Pt[i].GetY();
            Pt2[i-1].SetXY(tx, ty); // Pt2[i-1] - важливо
        }

        // звільнити пам'ять, виділену для Pt раніше
        delete[] Pt;

        // зменшити к-сть елементів на 1
        n--;

        // встановити нове значення Pt
        // перенаправити Pt на Pt2
        Pt = Pt2;
    }

    // взяти точку з позиції index
    Point GetPoint(int index)
    {
        if ((index >= 0) && (index < n))
            return Point(Pt[index].GetX(), Pt[index].GetY());
        else
            return Point(0.0, 0.0);
    }

    // метод, що виводить масив
    void Show(void)
    {
        double x, y;
        int i;

        for (i = 0; i < n; i++)
        {
            x = Pt[i].GetX();
            y = Pt[i].GetY();
            cout << "x" << i + 1 << " = " << x << ", ";
            cout << "y" << i + 1 << " = " << y;
            cout << endl;
        }
    }

    // операторна функція, що перевантажує оператор ->
    Polygon* operator->()
    {
        return this;
    }
};

void main()
{
    // перевантаження оператора доступу за покажчиком ->
    Polygon Pol; // екземпляр класу Polygon
    double x, y;
    int i;
    Point Pt;

    // сформувати довільний масив з 5 точок
    for (i = 0; i < 5; i++)
    {
        x = (double)(i * 2);
        y = (double)(i + 3);

        Pt.SetXY(x, y); // сформувати точку

        // виклик операторної функції operator->
        Pol->Add(Pt); // додати точку
    }

    cout << "Array of points: \n";

    // вивести масив через звертання до операторної функції operator->()
    Pol->Show();

    // видалити точку в позиції 2
    Pol->Delete(2);

    cout << "Modified array: \n";
    cout << "n = " << Pol->GetN() << endl;

    // знову вивести масив
    Pol->Show();
}

Результат виконання програми

Array of points:
x1 = 0, y1 = 3
x2 = 2, y2 = 4
x3 = 4, y3 = 5
x4 = 6, y4 = 6
x5 = 8, y5 = 7
Modified array:
n =4
x1 = 0, y1 = 3
x2 = 2, y2 = 4
x3 = 6, y3 = 6
x4 = 8, y4 = 7

 

4. Особливості перевантаження оператора ‘ , ‘ (кома). Операторна функція operator,()

У мові C++ оператор ‘ , ‘ може бути перевантажений. При перевантаженні оператора ‘ , ‘ у класі має бути оголошена операторна функція operator,(). В тіло операторної функції можна помістити будь-який код. Тобто, оператор ‘ , ‘ при бажанні може виконувати будь-які нестандартні операції над об’єктами класу.
У стандартному випадку оператор ‘ , ‘ використовується в операції присвоєння за зразком

obj1 = (obj2, obj3, ..., objN);

де obj1, …, objN – екземпляри деякого класу.

У випадку стандартного використання оператора ‘ , ‘ потрібно врахувати наступні особливості:

  • оператор ‘ , ‘ вважається бінарним. Тому операторна функція operator,() отримує один параметр;
  • при використанні перевантаженого оператора ‘ , ‘ в операції присвоєння приймається до уваги останній аргумент (цей аргумент є результатом оператора). Усі інші аргументи ігноруються.

В загальному при перевантаженні оператора ‘ , ‘ клас має такий вигляд

class ClassName
{

    // ...

    // операторна функція, яка перевантажує оператор ','
    ClassName operator,(ClassName obj)
    {
        // ...
    }
};

де

  • ClassName – ім’я класу, в якому перевантажується оператор ‘ , ‘;
  • obj – ім’я екземпляру класу, що передається як параметр в операторну функцію operator,().

 

5. Приклад перевантаження оператора ‘ , ‘

У прикладі перевантажується оператор ‘,’ в класі Coords3D, що реалізує координати в просторі. У класі оголошуються:

  • три внутрішні приховані (private) змінні з іменами x, y, z;
  • два конструктори класу;
  • метод доступу Get(), призначений для отримання значень x, y, z;
  • операторна функція operator,(), яка перевантажує оператор ‘ , ‘.

Вигляд програми для додатку типу Console Application наступний

#include <iostream>
using namespace std;

// клас, що визначає координати точки в просторі
class Coords3D
{
private:
    double x, y, z;

public:
    Coords3D()
    {
        x = y = z = 0;
    }

    Coords3D(double x, double y, double z)
    {
        this->x = x;
        this->y = y;
        this->z = z;
    }

    // метод читання x, y, z
    void Get(double& x, double& y, double& z)
    {
        x = this->x;
        y = this->y;
        z = this->z;
    }

    // перевантажений оператор ,
    Coords3D operator,(Coords3D obj)
    {
        Coords3D tmp;
        tmp.x = obj.x;
        tmp.y = obj.y;
        tmp.z = obj.z;
        return tmp;
    }
};

void main()
{
    double x, y, z;
    Coords3D c1(1, 3, 5); // екземпляри класу Coords3D
    Coords3D c2(2, 4, 6);
    Coords3D c3;

    // виклик операторної функції c3.operator,(c2)
    c3 = (c1, c2); // у c3 записується c2

    // перевірка
    c3.Get(x, y, z); // x = 2, y = 4, z = 6

    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    cout << "z = " << z << endl;

    //------------------------
    //створити інший екземпляр
    Coords3D c4(10, 15, 20);

    c3 = (c2, c1, c4); // c3 <= c4

    // перевірка
    c3.Get(x, y, z); // x = 10, y = 15, z = 20

    cout << endl;
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    cout << "z = " << z << endl;
}

У вищенаведеному коді в рядку

c3 = (c1, c2);

викликається операторна функція c3.operator,(c2). Отже, до уваги приймається останній екземпляр c2. Екземпляр з іменем c1 ігнорується. Це стосується і рядка

c3 = (c2, c1, c4);

де об’єкту c3 присвоюються значення внутрішніх змінних об’єкта c4.

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

Результат роботи програми

x = 2
y = 4
z = 6

x = 10
y = 15
z = 20

 


Зв’язані теми