Перевантаження оператору присвоювання =. Приклади

Перевантаження оператору присвоювання =. Приклади


Зміст



1. Як працює оператор присвоєння для екземплярів класу? Що таке оператор присвоювання за замовчуванням?

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

Наприклад, для двох екземплярів класів mc1 та mc2

MyClass mc1;
MyClass mc2;

якщо викликати

mc1=mc2; // побітове копіювання mc2 в mc1

то екземпляри класів будуть вказувати на одну й туж ділянку пам’яті. Це пояснюється тим, що відбудеться побітове копіювання ділянки пам’яті на яку посилається mc2 в ділянку пам’яті на яку посилається mc1. У результаті чого, всі зміни в mc2 будуть відображатись в mc1, і, навпаки, всі зміни в mc1 будуть відображатись в mc2.

Якщо потрібно присвоювати екземпляри класів один одному таким чином, щоб вони були не зв’язані між собою, то для цього в класі потрібно реалізувати операторну функцію operator=(), яка буде виконувати відповідні операції.

 

2. Загальна форма перевантаження оператора присвоєння ‘=’, який реалізований для присвоювання об’єктів класу

Оператор присвоєння = може бути перевантажений для конкретного класу.
Загальна форма оператора присвоєння для класу з іменем CMyClass має вигляд:

class CMyClass
{
    // поля та методи класу CMyClass
    // ...

    // перевантажений оператор присвоєння =
    // операторна функція operator=()
    CMyClass operator=(CMyClass mc);
};

// реалізація операторної функції operator=()
CMyClass CMyClass::operator=(CMyClass mc)
{
    // інструкції
    // ...
}

Можна реалізацію операторної функції operator=() розмістити всередині класу CMyClass. У цьому випадку загальна форма класу CMyClass матиме наступний вигляд:

class CMyClass
{
    // поля та методи класу CMyClass
    // ...

    // перевантажений оператор присвоєння =
    // операторна функція operator=()
    CMyClass operator=(CMyClass mc)
    {
        // реалізація операторної функції
        // ...
    }
};

 

3. Загальна форма оператора присвоювання =, який присвоює об’єкту класу значення інших типів

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

class CMyClass
{
    // поля та методи класу CMyClass
    // ...

    // перевантажений оператор присвоєння =
    // операторна функція operator=()
    CMyClass operator=(type mc)
    {
        // реалізація операторної функції
        // ...
    }
};

тут type – деякий тип даних, який може бути базовим (int, float, double, …) чи типом класу, структури, зчислення.

 

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

Оголошується клас Complex, що містить дві внутрішні приховані (private) змінні. Ці змінні формують дійсну та уявну складові комплексного числа. У класі Complex оголоується операторна функція

Complex operator=(Complex cm)
{
    // ...
}

яка перевантажує оператор присвоєння = для цього класу. При виклику оператора присвоєння для екземплярів (об’єктів) класу Complex буде викликатись саме ця операторна функція.
Лістинг класу Complex наступний:

// перевантаження оператора =
class Complex
{
private:
    double real;
    double imag;

public:
    // конструктори
    Complex()
    {
        real = imag = 0.0;
    }

    Complex(double _real, double _imag)
    {
        real = _real;
        imag = _imag;
    }

    // методи доступу
    void GetComplex(double& _real, double& _imag)
    {
        _real = real;
        _imag = imag;
    }

    void SetComplex(double _real, double _imag)
    {
        real = _real;
        imag = _imag;
    }

    // перевантажений оператор '='
    Complex operator=(Complex cm)
    {
        // доступ за посиланням на клас
        real = cm.real;
        imag = cm.imag;
        return *this; // повертає об'єкт, що згенерував виклик
    }
};

Використання класу Complex та його операторної функції operator=() в іншому методі може бути, наприклад, таким

// перевантаження оператора =
Complex c1(2.5, 3.8);
Complex c2;
double i,r;

i=r=0.0;
c1.GetComplex(r,i);

// виклик операторної функції, що перевантажує оператор =
c2 = c1;
c2.GetComplex(r, i); // r = 2.5; i = 3.8

// покажчик на клас Complex
Complex * pc3 = new Complex(1.1, 0.9);

c2 = *pc3; // перевантажений оператор присвоєння =
c2.GetComplex(r, i); // r = 1.1; i = 0.9

 

5. Приклад перевантаження оператора присвоєння для класу, що містить масив чисел

У прикладі для класу CArrayFloat100 реалізується операторна функція operator=(), яка перевантажує масив чисел типу float. З метою спрощення коду, у класі вибрано фіксований масив чисел (100 елементів). Операторна функція здійснює поелементне присвоєння елементів масивів.

Лістинг класу CArrayFloat100 наступний:

class ArrayFloat100
{
private:
    float A[100]; // масив зі 100 чисел
    int n; // розмір масиву

public:
    // конструктори
    ArrayFloat100()
    {
        n = 0;
        for (int i=0; i<100; i++)
            A[i] = 0.0f;
    }

    ArrayFloat100(int _n, float _A[])
    {
        for (int i=0; i<_n; i++)
            A[i] = _A[i];
        n = _n;
    }

    // методи доступу
    void SetAi(int index, float value)
    {
        if ((index>=0)&&(index<100)) A[index] = value;
            return;
    }

    float GetAi(int index)
    {
        if ((index>=0)&&(index<100))
            return A[index];
        return 0;
    }

    int GetN(void) { return n; }

    ArrayFloat100 operator=(ArrayFloat100 af)
    {
        n = af.n;
        for (int i=0; i<n; i++)
            A[i] = af.A[i];
        return *this;
    }
};

Демонстрація використання перевантаженого оператора присвоєння

// перевантаження оператора = для класу ArrayFloat100
float t;
float A[] = { 5.1, 2.3, 4.1, 1.5, 2.6, 3.3 };
int n;
int d;

n = 6;
ArrayFloat100 af1(n, A);
ArrayFloat100 af2;

// перевірка
t = af1.GetAi(2); // t = 4.1
t = af2.GetAi(2); // t = 0
d = af1.GetN(); // d = 6
d = af2.GetN(); // d = 0

// виклик перевантаженого оператора присвоювання
af2 = af1; // відбувається копіювання масивів

t = af2.GetAi(2); // t = 4.1
d = af2.GetN(); // d = 6

// af1 та af2 вказують на різні ділянки пам'яті - перевірка
af2.SetAi(2, 0.8);
t = af2.GetAi(2); // t = 0.8
t = af1.GetAi(2); // t = 4.1

 

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

Задано структуру BOOK, яка описує книгу. Також оголошується клас ArrayBooks, який реалізує динамічний масив типу BOOK. У класі реалізовано:

  • внутрішні змінні, які описують масив книг BOOK та його розмір;
  • конструктори;
  • методи доступу GetBook(), SetBook();
  • операторна функція operator=(), яка реалізує присвоєння екземплярів класу типу BOOK.

Лістинг програми типу Console Application, яка реалізує клас ArrayBooks наступний

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

struct BOOK
{
    string name; // назва книги
    string author; // автор книги
    int year; // рік випуску книги
    float price; // вартість книги
};

// клас, що реалізує масив книг типу BOOK
class ArrayBooks
{
private:
    BOOK * B; // динамічний масив книг
    int size; // розмір масиву

public:
    // конструктори класу
    ArrayBooks()
    {
        size = 0;
        B = NULL;
    }

    ArrayBooks(int _size, BOOK _B[])
    {
        int i;
        size = _size;

        // виділити пам'ять для масиву
        B = new BOOK[size];
        for (i=0; i<_size; i++)
            B[i] = _B[i];
    }

    ArrayBooks(int _size)
    {
        size = _size;
        B = new BOOK[size];
        for (int i=0; i<size; i++)
        {
            B[i].author = "";
            B[i].name = "";
            B[i].price = 0.0f;
            B[i].year = 0;
        }
    }

    // методи доступу
    BOOK GetBook(int index)
    {
        if ((index>=0) && (index<size))
            return B[index];
    }

    BOOK SetBook(int index, BOOK _B)
    {
        B[index] = _B;
    }

    // операторна функція operator=()
    ArrayBooks operator=(ArrayBooks A)
    {
        int i;

        // звільнити попередньо виділену пам'ять
        if (size>0)
            delete[] B;
        size = A.size;
        B = new BOOK[size];

        for (i=0; i<size; i++)
        {
            B[i]=A.B[i];
        }
        return *this;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    // перевантаження оператора присвоєння '='
    BOOK BB[] = {
        { "title-1", "author-1", 2012, 100.0f },
        { "title-2", "author-2", 2013, 150.0f },
        { "title-3", "author-3", 2014, 200.0f }
    };

    ArrayBooks AB1(3, BB); // екземпляр класу  ArrayBooks
    ArrayBooks AB2(4);
    int year;
    int price;
    string author;
    string name;

    // перевірка значень AB1
    BOOK B;
    B = AB1.GetBook(1);
    year = B.year; // year = 2013
    author = B.author; // author = "author-2"
    price = B.price; // price = 150.0
    name = B.name; // name = "title-2"

    // перевірка значень AB2
    B = AB2.GetBook(2);
    year = B.year; // year = 0
    author = B.author; // author = ""
    price = B.price; // price = 0
    name = B.name; // name=""

    // ВИКЛИК ПЕРЕВАНТАЖЕНОЇ ОПЕРАТОРНОЇ ФУНКЦІЇ operator=()
    AB2 = AB1;

    // перевірка значень AB2
    B = AB2.GetBook(2);
    year = B.year; // year = 2014
    author = B.author; // author = "author-3"
    price = B.price; // price = 200.0
    name = B.name; // name = "title-3"

    cout << "year = " << year << endl;
    cout << "name = " << name << endl;
    cout << "price = " << price << endl;
    cout << "author = " << author << endl;
    return 0;
}

 

7. Чи можна реалізовувати перевантажений оператор присвоєння для класу, якщо цей оператор оголошений як “дружній” до цього класу?

Оператор присвоєння не може бути оголошений як “дружній” до класу. Якщо перевантажити оператор присвоєння як “дружній” до класу, то це означає, що буде перевантажений глобальний оператор присвоєння, який викликається для екземплярів класів автоматично. Це, в свою чергу, може призвести до плутанини в операціях присвоєння та збільшенні невидимих помилок. Тому, компілятори мови C++ не допускають перевантажувати оператор присвоєння як “дружній” до класу.

 

8. Чи успадковується перевантажений оператор присвоєння похідними класами?

Ні, не успадковується.

 

9. Що відбудеться, якщо викликати оператор присвоєння для екземплярів класу, в якому не реалізована операторна функція operator=(), яка перевантажує оператор присвоєння =?

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

 

10. Приклад перевантаження оператора присвоювання в класі, в якому операторна функція в якості параметру отримує змінну (об’єкт) базового типу (int)

У прикладі демонструється перевантаження оператора присвоювання для класу Value. Перевантажений оператор присвоювання реалізує присвоювання цілочисельного значення екземпляру класу Value. У цьому випадку операторна функція має вигляд:

// операторна функція, яка отримує параметр типу int
Value operator=(int d)
{
    // ...
}

Оголошення класу Value та демонстрація його застосування у функції _tmain() наступні:

#include "stdafx.h"
#include <iostream>
using namespace std;

class Value
{
private:
    int d;

public:
    // конструктори
    Value() { d = 0; }
    Value(int _d) { d = _d; }

    int Get(void) { return d; }
    void Set(int _d) { d = _d; }

    // операторна функція, яка отримує параметр типу int
    Value operator=(int d2)
    {
        d = d2;
        return *this;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    // перевантаження операторної функції operator=()
    // яка отримує параметр базового типу
    Value v1(5); // екземпляр класу Value
    Value v2;
    int t;

    // перевірка
    t = v2.Get(); // t = 0

    // ВИКЛИК ОПЕРАТОРНОЇ ФУНКЦІЇ operator=(int) КЛАСУ Value
    v2 = 8;

    // перевірка
    t = v2.Get(); // t = 8

    // ВИКЛИК ОПЕРАТОРА ПРИСВОЮВАННЯ, ЯКИЙ ЗГЕНЕРОВАНИЙ ЗА ЗАМОВЧУВАННЯМ
    v2 = v1; // ПОБАЙТОВЕ КОПІЮВАННЯ

    // перевірка
    t = v2.Get(); // t = 5
    cout << "t = " << t << endl;
    return 0;
}

У вищенаведеному коді, у функції _tmain() оператор присвоєння для класу Value викликається два рази. Перший раз викликається перевантажена в класі операторна функція operator=() у рядку

v2 = 8;

Другий раз викликається оператор, що для класу Value генерується компілятором за замовчуванням

v2 = v1;

Цей оператор виконує побайтову операцію копіювання.

 


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