Бьерн Страуструп. Язык программирования С++



бет60/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   56   57   58   59   60   61   62   63   ...   124

7.7 Индексация


Операторная функция 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() должна быть функцией-членом.





Достарыңызбен бөлісу:
1   ...   56   57   58   59   60   61   62   63   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет