Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

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(), другим больше нравится перегрузка операции ++ , которая позволяет получить итератор, используемый как указатель (см. $$8.8). Кроме того, операторная функция operator() активно используется для выделения подстрок и индексации многомерных массивов.       Функция operator() должна быть функцией-членом.

7.9 Косвенное обращение

Операцию косвенного обращения к члену -> можно определить как унарную постфиксную операцию. Это значит, если есть класс       class Ptr {       // ...       X* operator->();       }; объекты класса Ptr могут использоваться для доступа к членам класса X также, как для этой цели используются указатели:       void f(Ptr p)       {       p->m = 7; // (p.operator->())->m = 7       } Превращение объекта p в указатель p.operator->() никак не зависит от члена m, на который он указывает. Именно по этой причине operator->() является унарной постфиксной операцией. Однако, мы не вводим новых синтаксических обозначений, так что имя члена по-прежнему должно идти после -> :       void g(Ptr p)       {       X* q1 = p->; // синтаксическая ошибка       X* q2 = p.operator->(); // нормально       }       Перегрузка операции -> прежде всего используется для создания "хитрых указателей", т.е. объектов, которые помимо использования как указатели позволяют проводить некоторые операции при каждом обращении к указуемому объекту с их помощью. Например, можно определить класс RecPtr для организации доступа к объектам класса Rec, хранимым на диске. Параметром конструктора RecPtr является имя, которое будет использоваться для поиска объекта на диске. При обращении к объекту с помощью функции RecPtr::operator->() он переписывается в основную память, а в конце работы деструктор RecPtr записывает измененный объект обратно на диск.       class RecPtr {       Rec* in_core_address;       const char* identifier;       // ...       public:       RecPtr(const char* p)       : identifier(p) { in_core_address = 0; }       ~RecPtr()       { write_to_disc(in_core_address,identifier); }       Rec* operator->();       };       Rec* RecPtr::operator->()       {       if (in_core_address == 0)       in_core_address = read_from_disc(identifier);       return in_core_address;       } Использовать это можно так:       main(int argc, const char* argv)       {       for (int i = argc; i; i--) {       RecPtr p(argv[i]);       p->update();       }       } На самом деле, тип RecPtr должен определяться как шаблон типа (см. $$8), а тип структуры Record будет его параметром. Кроме того, настоящая программа будет содержать обработку ошибок и взаимодействие с диском будет организовано не столь примитивно.       Для обычных указателей операция -> эквивалентна операциям, использующим * и []. Так, если описано       Y* p; то выполняется соотношение       p->m == (*p).m == p[0].m Как всегда, для определенных пользователем операций такие соотношения не гарантируются. Там, где все-таки такая эквивалентность требуется, ее можно обеспечить:       class X {       Y* p;       public:       Y* operator->() { return p; }       Y& operator*() { return *p; }       Y& operator[](int i) { return p[i]; }       };       Если в вашем классе определено более одной подобной операции, разумно будет обеспечить эквивалентность, точно так же, как разумно предусмотреть для простой переменной x некоторого класса, в котором есть операции ++, += = и +, чтобы операции ++x и x+=1 были эквивалентны x=x+1.       Перегрузка -> как и перегрузка [] может играть важную роль для целого класса настоящих программ, а не является просто экспериментом ради любопытства. Дело в том, что в программировании понятие косвенности является ключевым, а перегрузка -> дает ясный, прямой и эффективный способ представления этого понятия в программе. Есть другая точка зрения на операцию ->, как на средство задать в С++ ограниченный, но полезный вариант понятия делегирования (см. $$12.2.8 и 13.9).