Опубликован: 10.10.2006 | Доступ: свободный | Студентов: 6020 / 456 | Оценка: 4.26 / 3.88 | Длительность: 31:30:00
Лекция 7:

Перегрузка операций

7.7 Индексация

Операторная функция operator[] задает для объектов классов интерпретацию индексации. Второй параметр этой функций (индекс) может иметь произвольный тип. Это позволяет, например, определять ассоциативные массивы. В качестве примера можно переписать определение из \S 2.3.10, где ассоциативный массив использовался в небольшой программе, подсчитывающей число вхождений слов в файле.

Там для этого использовалась функция. Мы определим настоящий тип ассоциативного массива:

class assoc {
   struct pair {
      char* name;
      int val;
   };

   pair* vec;
   int max;
   int free;

   assoc(const assoc&);            // предотвращает копирование
   assoc& operator=(const assoc&); // предотвращает копирование
public:
   assoc(int);
   int& operator[](const char*);
   void print_all();
 };

В объекте assoc хранится вектор из структур pair размером max.

В переменной free хранится индекс первого свободного элемента вектора.

Чтобы предотвратить копирование объектов assoc, конструктор копирования и операция присваивания описаны как частные. Конструктор выглядит так:

assoc::assoc(int s)
{
  max = (s<16) ? 16 : s;
  free = 0;
  vec = new pair[max];
}

В реализации используется все тот же неэффективный алгоритм поиска, что и в \S 2.3.10. Но теперь, если вектор переполняется, объект assoc увеличивается:

#include <string.h>

int& assoc::operator[](const char* p)
/*
  работает с множеством пар (структур pair):
  проводит поиск p, возвращает ссылку на
  целое значение из найденной пары,
  создает новую пару, если p не найдено
*/
{
  register pair* pp;

  for (pp=&vec[free-1]; vec<=pp; pp-- )
      if (strcmp(p,pp->name) == 0) return pp->val;
  if (free == max) { //переполнение: вектор увеличивается
     pair* nvec = new pair[max*2];
     for (int i=0; i<max; i++) nvec[i] = vec[i];
     delete vec;
     vec = nvec;
     max = 2*max;
  }

  pp = &vec[free++];
  pp->name = new char[strlen(p)+1];
  strcpy(pp->name,p);
  pp->val = 0;    // начальное значение = 0
  return pp->val;
}

Поскольку представление объекта assoc скрыто от пользователя, нужно иметь возможность напечатать его каким-то образом. В следующем разделе будет показано как определить настоящий итератор для такого объекта.

Здесь же мы ограничимся простой функцией печати:

void assoc::print_all()
{
  for (int i = 0; i<free; i++)
      cout << vec[i].name << ": " << vec[i].val << '\n';
}

Наконец, можно написать тривиальную программу:

main() // подсчет числа вхождений во входной
                   // поток каждого слова
            {
              const MAX = 256;  // больше длины самого длинного слова
              char buf[MAX];
              assoc vec(512);
              while (cin>>buf) vec[buf]++;
              vec.print_all();
            }

Опытные программисты могут заметить, что второй комментарий можно легко опровергнуть. Решить возникающую здесь проблему предлагается в упражнении \S 7.14 [20]. Дальнейшее развитие понятие ассоциативного массива получит в \S 8.8.

Функция operator[]() должна быть членом класса. Отсюда следует, что эквивалентность x[y] == y[x] может не выполняться, если x объект класса. Обычные отношения эквивалентности, справедливые для операций со встроенными типами, могут не выполняться для пользовательских типов ( \S 7.2.2, см. также \S 7.9).

7.8 Вызов функции

Вызов функции, т.е. конструкцию выражение(список-выражений), можно рассматривать как бинарную операцию, в которой выражение является левым операндом, а список-выражений - правым. Операцию вызова можно перегружать как и другие операции. В функции operator ()() список фактических параметров вычисляется и проверяется по типам согласно обычным правилам передачи параметров. Перегрузка операции вызова имеет смысл прежде всего для типов, с которыми возможна только одна операция, а также для тех типов, одна из операций над которыми имеет настолько важное значение, что все остальные в большинстве случаев можно не учитывать.

Мы не дали определения итератора для ассоциативного массива типа assoc. Для этой цели можно определить специальный класс assoc_iterator, задача которого выдавать элементы из assoc в некотором порядке. В итераторе необходимо иметь доступ к данным, хранимым в assoc, поэтому он должен быть описан как friend:

class assoc {
friend class assoc_iterator;
    pair* vec;
    int max;
    int free;
public:
    assoc(int);
    int& operator[](const char*);
};

Итератор можно определить так:

class assoc_iterator {
   const assoc* cs;   // массив assoc
   int i;             // текущий индекс
public:
   assoc_iterator(const assoc& s) { cs = &s; i = 0; }
   pair* operator()()
     { return (i<cs->free)? &cs->vec[i++] : 0; }
};

Массив assoc объекта assoc_iterator нужно инициализировать, и при каждом обращении к нему с помощью операторной функции () будет возвращаться указатель на новую пару (структура pair ) из этого массива. При достижении конца массива возвращается 0:

main()  // подсчет числа вхождений во входной
        // поток каждого слова
{
  const MAX = 256;  // больше длины самого длинного слова
  char buf[MAX];
  assoc vec(512);
  while (cin>>buf) vec[buf]++;
  assoc_iterator next(vec);
  pair* p;
  while ( p = next(vec) )
      cout << p->name << ": " << p->val << '\n';
}

Итератор подобного вида имеет преимущество перед набором функций, решающим ту же задачу: итератор может иметь собственные частные данные, в которых можно хранить информацию о ходе итерации. Обычно важно и то, что можно одновременно запустить сразу несколько итераторов одного типа.

Конечно, использование объектов для представления итераторов непосредственно никак не связано с перегрузкой операций. Одни предпочитают использовать тип итератора с такими операциями, как first(), next() и last(), другим больше нравится перегрузка операции ++, которая позволяет получить итератор, используемый как указатель (см. \S 8.8). Кроме того, операторная функция operator() активно используется для выделения подстрок и индексации многомерных массивов.

Функция operator() должна быть функцией-членом.

Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Кирилл Пшенцов
Кирилл Пшенцов
Россия, г. Москва
Антон Щанкин
Антон Щанкин
Россия