Операторная функция operator[] задает для объектов классов
интерпретацию индексации. Второй параметр этой функций (индекс) может
иметь произвольный тип. Это позволяет, например, определять
ассоциативные массивы. В качестве примера можно переписать
определение из $$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];
}
В реализации используется все тот же неэффективный алгоритм поиска,
что и в $$2.3.10. Но теперь, если вектор переполняется, объект
assoc увеличивается:
#include
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
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
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();
}
Опытные программисты могут заметить, что второй комментарий можно
легко опровергнуть. Решить возникающую здесь проблему предлагается
в упражнении $$7.14 [20]. Дальнейшее развитие понятие ассоциативного
массива получит в $$8.8.
Функция operator[]() должна быть членом класса. Отсюда следует,
что эквивалентность x[y] == y[x] может не выполняться, если
x объект класса. Обычные отношения эквивалентности, справедливые
для операций со встроенными типами, могут не выполняться для
пользовательских типов ($$7.2.2, см. также $$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 (ifree)? &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(), другим больше нравится перегрузка операции
++ , которая позволяет получить итератор, используемый как указатель
(см. $$8.8). Кроме того, операторная функция operator() активно
используется для выделения подстрок и индексации многомерных массивов.
Функция operator() должна быть функцией-членом.
Достарыңызбен бөлісу: |