|
Программирование >> Поддержка объектно-ориентированного программирования
значения s, для этого надо вызвать конструктор копирования для string. Для получения еще одной копии значения s по выходе из g() нужен еще один вызов конструктора string(const string&). На этот раз инициализируется временная переменная, которая затем присваивается s. Для оптимизации одну, но не обе, из подобных операций копирования можно убрать. Естественно, временные переменные, используемые для таких целей, уничтожаются надлежащим образом деструктором string::~string() (см. $$R.12.2). Если в классе X операция присваивания X::operator=(const X&) и конструктор копирования X::X(const X&) явно не заданы программистом, недостающие операции будут созданы транслятором. Эти созданные функции будут копировать по членам для всех членов класса X. Если члены принимают простые значения, как в случае комплексных чисел, это, то, что нужно, и созданные функции превратятся в простое и оптимальное поразрядное копирование. Если для самих членов определены пользовательские операции копирования, они и будут вызываться соответствующим образом: class Record { string name, address, profession; ... void f(Record& r1) Record r2 = r1; Здесь для копирования каждого члена типа string из объекта r1 будет вызываться string::operator=(const string&). В нашем первом и неполноценном варианте строковый класс имеет член-указатель и деструктор. Поэтому стандартное копирование по членам для него почти наверняка неверно. Транслятор может предупреждать о таких ситуациях. 7.7 Индексация Операторная функция operator[] задает для объектов классов интерпретацию индексации. Второй параметр этой функций (индекс) может иметь произвольный тип. Это позволяет, например, определять ассоциативные массивы. В качестве примера можно переписать определение из $$2.3.1 0, где ассоциативный массив использовался в небольшой программе, подсчитывающей число вхождений слов в файле. Там для этого использовалась функция. Мы определим настоящий тип ассоциативного массива: 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 <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; 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(); больше длины самого длинного слова Опытные программисты могут заметить, что второй комментарий можно легко опровергнуть. Решить возникающую здесь проблему предлагается в упражнении $$7.14 [20]. Дальнейшее развитие понятие ассоциативного массива получит в $$8.8. Функция operator[]() должна быть членом класса. Отсюда следует, что эквивалентность x[y] == y[x] может не выполняться, если x объект класса. Обычные отношения эквивалентности, справедливые для операций со встроенными типами, могут не выполняться для пользовательских типов ($$7.2.2, см. также $$7.9). 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() активно используется для выделения подстрок и индексации многомерных массивов. 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() подсчет числа вхождений во входной поток каждого слова
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |