Перевантаження оператору індексування елементів масиву

Перевантаження оператору індексування елементів масиву [ ]


Зміст



1. Які обмеження накладаються на перевантаження оператору [ ]?

На перевантаження оператору [] накладаються наступні обмеження:

  • до цього оператора заборонено застосовувати “дружні” функції;
  • операторна функція, що перевантажують цей оператор, має бути нестатичною функцією-членом.

 

2. Перевантаження оператора індексування елементів масиву [ ]. Особливості використання

У мові програмування C++ є можливість перевантажувати оператор індексування елементів масиву [ ]. Цей оператор вважається унарним, тобто потребує одного параметра — індексу масиву. Отже, доцільно перевантажувати оператор [ ] в класах, де використовуються масиви.

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

Наприклад. Нехай задано клас з іменем A. У класі перевантажено оператор індексування масиву [ ]. Тоді використання екземпляру класу A може бути таким:

obj[d]

де

  • obj – екземпляр (об’єкт) класу A;
  • d – індекс. Це є параметр, що передається операторній operator[]() функції класу A.

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

obj[d]

перетворюється на виклик

obj.operator[](d)

 

3. Варіанти реалізації операторної функції operator[]() в класі. Загальна форма

Для того, щоб перевантажити оператор індексування елементів масиву [], в класі повинна бути реалізована операторна функція operator[](). Існує 2 варіанти реалізації операторної функції operator[](). Ці варіанти відрізняються поверненням значення з операторної функції. Операторна функція отримує один параметр, який є індексом масиву.

Варіант 1. Цей варіант застосовується, якщо оператор [ ] має бути використаний у правій (і тільки у правій) частині оператора присвоювання. Тобто значення елементу масиву в класі не змінюється.

У цьому випадку операторна функція operator[]() повертає значення деякого типу. Загальна форма функції при її реалізації в класі

class ClassName
{

    // ...

    // операторна функція повертає значення типу type
    type operator[](int d)
    {
        // ...
    }
};

Після цього, можна використовувати екземпляр класу ClassName наступним чином:

x = obj[3];

де

  • x – деяка змінна типу type;
  • obj – екземпляр (об’єкт) класу ClassName.

Варіант 2. Цей варіант застосовується у випадку, коли потрібно щоб оператор [] розміщувався у лівій та правій частині оператора присвоювання. Наявність оператора індексування [] у лівій частині оператора присвоювання означає, що в об’єкті класу можна змінювати значення елементу масиву за індексом.
Відмінність від попереднього варіанту (Варіант 1) полягає у тому, що функція повертає посилання & на тип. Загальна форма оголошення операторної функції наступна

class ClassName
{

    // ...

    // операторна функція повертає посилання (&) на тип type
    type& operator[](int d)
    {
        // ...
    }
};

Після такого оголошення можна використовувати екземпляр класу ClassName наступним чином:

// використання оператора [] для об’єкту класу
x = obj[3]; // у правій частині оператора присвоювання
obj[5] = y; // у лівій частині оператора присвоювання

де

  • x – деяка змінна типу type;
  • obj – екземпляр (об’єкт) класу ClassName.

 

4. Приклади перевантаження оператора індексування елементів масиву []

Приклад 1. Приклад перевантаження оператора [] у класі. Клас реалізує масив чисел типу int. Максимальна кількість елементів масиву рівна 10. Операторна функція класу повертає посилання на тип int. Це означає, що оператор [] можна використовувати у лівій частині оператора присвоювання.

У класі Array10 оголошуються:

  • A – внутрішній масив 10 цілих чисел;
  • n – кількість елементів у масиві;
  • конструктори класу;
  • метод GetN(). Цей метод призначений для читання значення n;
  • методи SetAi() та GetAi() для доступу до елементів масиву A;
  • операторна функція operator[](). Ця функція перевантажує оператор доступу до елементу масиву за його індексом.

Текст програми наступний:

#include <iostream>
using namespace std;

// клас, що реалізує масив з 10 цілих чисел
class Array10
{
private:
    int A[10]; // масив
    int n; // к-сть елементів у масиві

public:
    // конструктори класу
    // конструктор без параметрів
    Array10()
    {
        n = 0;
    }

    // конструктор з 1 параметром
    Array10(int _n)
    {
        if ((_n >= 0) && (_n <= 10))
        {
            n = _n;
            for (int i = 0; i < n; i++)
                A[i] = 0; // занулити масив
        }
        else
            n = 0;
    }

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

    // записати в елемент масиву значення
    void SetAi(int index, int item)
    {
        if ((index>=0)&&(index<10))
            A[index] = item;
    }

    // зчитати значення по індексу
    // внутрішній метод
    int GetAi(int index)
    {
        return A[index];
    }

    // перевантаження оператора [] отримання елемента масиву
    // операторна функція, реалізована всередині класу
    int& operator[](int index)
    {
        return A[index];
    }
};

void main()
{
    // оператор індексування масиву []
    Array10 A1(10);

    // сформувати масив A1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
    for (int i = 1; i <= 10; i++)
        A1[i - 1] = i; // виклик операторної функції operator[]() класу Array10

    // перевірка
    int t;

    t = A1[3]; // виклик операторної функції, t = 4

    // Оператор [] у лівій частині оператора присвоювання
    A1[5] = 205;
    t = A1.GetAi(5); // t = 205

    cout << "t = " << t << endl;
}

Як видно з прикладу, операторна функція повертає посилання на тип int

// операторна функція - повертає int&
int& operator[](int index)
{
    return A[index];
}

Це означає, що оператор [ ] можна використовувати у лівій частині оператора присвоювання

...

// Оператор [] у лівій частині оператора присвоювання
A1[5] = 205;

...

Якщо операторну функцію реалізувати так, що вона повертає значення int (а не посилання int&)

// операторна функція - повертає int
int operator[](int index)
{
    return A[index];
}

то використовувати оператор [ ] у лівій частині оператора присвоювання буде заборонено

...

// Оператор [] у лівій частині оператора присвоювання
A1[5] = 205; // помилка, заборонено!

t = A1[3]; // так добре, можна

...

Приклад 2. Перевантаження оператора індексування елементів масиву [ ] для масиву структур типу BOOK.

У прикладі оголошуються:

  • структура типу BOOK, що містить інформацію про книгу;
  • клас ArrayBooks, який реалізує динамічний масив книг типу BOOK.

У класі ArrayBooks реалізовано:

  • внутрішню змінну-покажчик на тип BOOK;
  • внутрішню змінну n, яка визначає кількість книг у масиві;
  • два конструктори;
  • метод GetN(), який повертає кількість книг n у масиві;
  • операторна функція operator[](). З допомогою цієї функції здійснюється доступ до заданого елементу масиву. Функція перевантажує оператор доступу за індексом [].

Текст програми, створеної за шаблоном Console Application наступний:

#include <iostream>
#include <stdio.h>
using namespace std;

// структура, що описує книгу
struct BOOK
{
    string title; // назва книги
    int year; // рік видання
    float price; // вартість книги
};

// клас, що реалізує масив книг типу BOOK
// містить перевантажену операторну функцію operator[]()
class ArrayBooks
{
private:
    BOOK * B; // пам'ять для масиву виділяється динамічно
    int n; // кількість книг

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

    ArrayBooks(int _n)
    {
        n = _n;

        // виділити пам'ять для покажчиків на BOOK
        B = (BOOK*) new BOOK[n];

        // заповнити кожну книгу пустими значеннями
        for (int i=0; i<n; i++)
        {
            B[i].title = "";
            B[i].price = 0.00;
            B[i].year = 0;
        }
    }

    // деструктор
    ~ArrayBooks()
    {
        // звільнити пам'ять, виділену під масив структур
        if (n>0)
            delete[] B;
    }

    // метод, що повертає значення n
    int GetN(void) { return n; }

    // операторна функція, яка перевантажує оператор індексування []
    // функція повертає BOOK&, для того щоб оператор [] можна було використовувати
    // в лівій частині оператора присвоювання
    BOOK& operator[](int index)
    {
        if ((index>=0)&&(index<n))
            return B[index];
        else
        {
            // повернути пусту структуру
            BOOK BB;
            BB.title = "";
            BB.price = 0.0;
            BB.year = 0;
            return BB;
        }
    }
};

void main()
{
    // демонстрація перевантаження оператора []
    ArrayBooks AB(3); // створити об'єкт класу ArrayBooks, у масиві 3 елементи

    // виклик операторної функції operator[](), сформувати значення
    // книга - 1
    AB[0].title = "This is a first book";
    AB[0].price = 99.99;
    AB[0].year = 2999;

    // книга - 2
    AB[1].title = "This is a second book";
    AB[1].price = 125.95;
    AB[1].year = 2020;

    // книга - 3
    AB[2].title = "Third book";
    AB[2].price = 777.77;
    AB[2].year = 2000;

    // перевірка
    string title;
    double price;
    int year;

    title = AB[1].title; // title = "This is a second book"
    price = AB[1].price; // price = 125.95
    year = AB[1].year; // year = 2020

    // вивести на екран
    cout << "Title = " << title.c_str() << ::endl;
    cout << "Price = " << price << ::endl;
    cout << "Year = " << year << ::endl;
}

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

Title = This is a second book
Price = 125.95
Year = 2020

 

5. Які вимоги до параметру операторної функції operator[]()?

Операторна функція, яка перевантажує оператор індексування елементів масиву, отримує параметр, який є індексом в масиві. Тип параметру не обов’язково має бути int. Допускається інший тип параметру в індексі масиву (наприклад, char).

 

6. Приклад класу, що містить операторну функцію operator[](), індекс якої є тип char

Цей приклад демонструє твердження, що тип індексу в операторній функції може бути не тільки int.
У прикладі реалізовано клас CharIndex, який містить масив з 26 елементів. У класі оголошується операторна функція operator[](), яка в якості індексу отримує значення типу char.

У програмі підраховується кількість входжень символів латинського алфавіту ‘a’..’z’ в заданому тексті.

Текст програми, створеної за шаблоном Console Application, наступний

#include <iostream>
using namespace std;

// доступ за індексом в операторній функції - клас CharIndex
class CharIndex
{
private:
    int A[26]; // масив частоти входжень символу в тексті

public:
    CharIndex()
    {
        for (int i=0; i<26; i++)
            A[i] = 0;
    }

    // операторна функція - доступ за типом char
    int& operator[](char c)
    {
        int position; // позиція в масиві A
        position = (int)c - int('a');
        return A[position];
    }
};

void main(void)
{
    // демонстрація твердження,
    // що тип індексу не обов'язково має бути int
    CharIndex cI; // об'єкт класу CharIndex
    string text; // заданий текст
    int i;
    char sym;

    // задано деякий текст
    text = "C++ is the best language in the world!";

    // обчислити кількість входжень символів 'a'..'z' у тексті
    for (i = 0; i<text.length(); i++)
        cI[text[i]]++; // виклик операторної функції

    // вивід, виводяться тільки ненульові значення
    for (i=0; i<26; i++)
    {
        sym = 'a' + i; // взяти індекс
        if (cI[sym]>0)
            cout << sym << " = " << cI[sym] << ::endl;
    }
}

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

a = 2
b = 1
d = 1
e = 4
g = 2
h = 2
i = 2
l = 2
n = 2
o = 1
r = 1
s = 2
t = 3
u = 1
w = 1

 


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