Перегрузка оператора присваивания =. Примеры

Перегрузка оператора присваивания =. Примеры


Содержание



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.Get(); // 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;
        }
    }

    // конструктор копирования
    ArrayBooks(const ArrayBooks& _B)
    {
        size = _B.size;
        B = new BOOK[size];
        for (int i = 0; i < size; i++)
        B[i] = _B.B[i];
    }
    // методы доступа
    BOOK GetBook(int index)
    {
        if ((index>=0) && (index<size))
            return B[index];
    }

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

    // операторная функция operator=()
    ArrayBooks& operator=(const 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;
    }

    // деструктор
    ~ArrayBooks()
    {
        if (size > 0)
            delete[] B;
    }
};

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;

Этот оператор выполняет побайтовую операцию копирования.

 


Связанные темы