11.4. Полиморфизм

    11.4. Полиморфизм. Виртуальные функции

    «Мадмуазель Собак слыла культурной девушкой: в ее словаре было около ста восьмидесяти слов. При этом ей было известно одно такое слово, которое Эллочке даже не могло присниться. Это было богатое слово: полиморфизм»

    Вариации на тему Эллочки Людоедки.

    Полиморфизм Полиморфизм – действительно богатое понятие, хотя бы потому, что это свойство является динамическим, т.е. связанным со временем выполнения программы, а таковых механизмов в Си не так уж много. Слово полиморфный наиболее удачно переводится как многоликий, обычно, говоря о полиморфных методах (виртуальных функциях) обращают внимание на множественность методов с общим именем в системе классов (метод един во многих лицах). Но все-таки основная сущность полиморфизма состоит не в этом, а в возможности одновременно рассматривать один и тот же объект и как абстрактную сущность, на уровне которой он совместим с объектами других классов, и как сущность конкретную. Сам же механизм полиморфных методов обеспечивает автоматическое преобразование первого представления во второе во время работы программы.

    Свойство полиморфности заключается в том, что при отсутствии полной информации о том, к какому из классов относится объект, функция (метод) в состоянии идентифицировать его класс и корректно выполниться в нем Этим самым создается иллюзия функции «единой во многих лицах» - в каждом из родственных классов.

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

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

    Преобразуя указатель на объект производного класса к указателю на объект базового, мы получаем доступ к вложенному объекту базового класса. Такое преобразование еще называют расширением. Оно всегда корректно и именно поэтому может производиться без явного использования операции преобразования типа указателя. (остальные преобразования типов указателей должны быть явными). Побочный эффект такого преобразования состоит в том, что транслятор «забывает» об объекте производного класса и вместо переопределенных в нем методов вызывает методы базового:

    class A {

    public: void f1(){}

    };

    class B : A {

    public: void f1(){} // Переопределена в классe B

    void f2(){}

    };

    void main(){

    B x;

    A *pa = &x; // Прямое преобразование (расширение) - неявное

    pa->f1(); // Вызов A::f1(), хотя внутри объекта класса B

    pb = (B*) pa; // Обратное преобразование (сужение) - явное

    pb ->f2(); } // Корректно, если под pa был объект класса B

    После преобразования указателя на объект класса B в указатель на объект класса A происходит вызов функции из вложенного объекта базового класса A::f1(), хотя реально под указателем находится объект класса B.

    Обратное преобразование от указателя на базовый класс к указателю на производный может быть сделано только явно. Преобразование будет корректно, если данный объект базового класса действительно содержится в объекте того производного класса, к типу которого производится преобразование указателя. В противном случае возникает динамическая ошибка приведения типов. Это ошибка времени выполнения, она может возникать или не возникать в зависимости от «истории» выполнения программы.

    Идея преобразования типа указателя заключается в сохранении указателем адреса памяти и изменении ее интерпретации. При переходе от базового класса к производному это свойство может нарушаться, если объект базового класса находится «не в начале» объекта производного (например, при множественном наследовании). Понятно, что это проблема транслятора, но то, что она возникает, свидетельствует об особой интерпретации в языке преобразований такого вида.

    Виртуальные функции В Си++ полиморфизм является исключением, а не правилом в том смысле, что полиморфный метод (в терминах Си++ - виртуальная функция) должен быть предварен в объявлении в базовом классе ключевым словом virtual (в Java, например, полиморфизм является тотальным и реализован по умолчанию).

    Если говорить кратко, то при вызове виртуальной функции происходит автоматическое сужение до того производного класса, которому принадлежит объект (точнее, в котором последний раз она была переопределена). Развернутое определение виртуальной функции выглядит так:

    • TEXT
         виртуальная функция определена в базовом и переопределена (унаследована) в множестве производных классов;
    • TEXT
         вызов функции осуществляется через указатель на объект базового класса, который по правилам приведения может ссылаться на объект любого из производных классов;
    • TEXT
         вызов виртуальной функции сопровождается переходом от объекта базового класса к «окружающему» его объекту производного и вызов функции происходит именно в нем.

    Пусть имеется базовый класс A и производные классы B,C. В классе А определена функция f(), в классах B,C - унаследована и, возможно, переопределена. Пусть теперь имеется массив указателей на объекты базового класса - p. Он инициализирован как указателями на объекты класса A, так и на объекты производных классов B,C (точнее, на вложенные в них объекты базового класса A):

    TEXT
        class a                             { virtual  void f(){} };
    
        class b : public a               { ...   void f(){}     };
    
        class c : public a               { ...   void f(){}     };
    
        a  A1;       b  B1;   c C1;
    
        a  *p[3] = { &B1, &C1, &A1 };                  

    рис. 114.1. Виртуальная функция

    Как будет происходить вызов обычной невиртуальной функции при использовании указателей из этого массива? Очевидно, что транслятор, располагая исключительно информацией о том, что указуемыми переменными являются объекты базового класса A (что следует из определения массива), вызовет во всех случаях функцию a::f(). То же самое произойдет, если обрабатывать массив указателей в цикле:

    p[0]->f(); // Вызов a::f()во всех трех случаях

    p[1]->f(); // по указателю на объект базового класса

    p[2]->f();

    for (i=0; i<=2; i++) p[i]->f();

    Наличие указателя на объект базового класса A свидетельствует о том, что в данной точке программы транслятор не располагает информацией о том, объект какого из производных классов расположен под указателем. Тем не менее, если функция является виртуальной, то при вызове ее по указателю на объект базового класса она должна идентифицировать его производный класс и вызвать переопределенную функцию именно для этого класса:

    p[0]->f(); // вызов b::f() для B1

    p[1]->f(); // вызов c::f() для C1

    p[2]->f(); // вызов a::f() для A1

    for (i=0; i<2; i++) // вызов b::f(),c::f(),a::f(

    TEXT
            p[i]->f();             // в зависимости от типа объекта          

    Таким образом, при преобразовании типа «указатель на производный класс» к типу «указатель на базовый класс» происходит потеря информации о типе объекта производного класса, а при вызове виртуальной функции - обратный процесс неявного восстановления типа объекта. Объект базового класса должен быть доступен через указатель только по той причине, что это единственный в Си механизм, позволяющий ссылаться на объекты неопределенного вида.

    Механизм реализации виртуальных функций. Динамическое связывание Реализовать механизм виртуальных функций можно только на основе динамического связывания (см. 9.3). Синтаксически неизменный вызов виртуальной функции с одним и тем же именем в зависимости от типа указуемого объекта приводит вызову одноименной функции, но в разных классах. Действительно, механизм виртуальных функций реализуется через указатели на функции, которые связываются с объектом базового класса в момент его создания (то есть динамически, во время работы программы):

    • TEXT
         для каждой пары производный класс - базовый класс транслятором создается массив указателей на функции (таблица функций), каждой виртуальной функции соответствует в нем свое значение индекса (смещение);
    • TEXT
         в объекте базового класса имеется указатель на массив указателей на функции, куда при конструировании объекта производного класса записывается адрес таблицы его виртуальных функций;
    • TEXT
         если объект базового класса  расположен не в начале объекта производного класса, то перед вызовом виртуальной функции транслятор должен предусмотреть преобразование указателя с объекта базового класса на объект производного (например, использовать дополнительную таблицу смещений);
    • TEXT
         вызов виртуальной функции транслируется в вызов функции по указателю, извлеченному по фиксированному смещению из таблицы, связанной с объектом базового класса.

    рис.114-2 Механизм вызова виртуальных функций

    Сказанное можно проиллюстрировать средствами того же Си. Естественно, что модель не отражает всех свойств оригинала, например, различия интерфейсов виртуальных функций.

    // Выделены компоненты, создаваемые транслятором

    class A {

    void (**ftable)(); // Указатель на массив указателей

    public: // на виртуальные функции (таблицу функций)

    virtual void x();

    virtual void y();

    virtual void z();

    TEXT
        A();
    
        ~A(); };

    #define vx 0 // Индексы в массиве

    #define vy 1 // указателей на виртуальные функции

    #define vz 2

    // Таблица адресов функций класса А

    void (*TableA[])() = { A::x, A::y, A::z };

    A::A()

    { ftable = TableA; …} // Назначение таблицы для класса А

    class B : public A {

    public:

    TEXT
        void    x();
    
        void    z();
    
        B();
    
        ~B(); };

    // Таблица адресов функций класса B

    // A::y - наследуется из А, B::x - переопределяется в B

    void (*TableB[])() = { B::x, A::y, B::z };

    B::B() { A::ftable = TableB; …}

    TEXT
                                    // Назначение таблицы для класса B

    void main(){

    B nnn; // ссылается на объект производного класса B

    A *pa = &nnn; // Указатель p базового класса A

    pa->x(); } // реализация - (*pa->ftable[vx])();

    Механизм реализации виртуальных функций как нельзя лучше реализует единство алгоритма и данных. При «обычном» программировании необходимо всегда следить за соответствием алгоритм (вызов функции) – данные (фактические параметры). Есть всегда возможность ошибиться и «подсунуть» функции данные не того формата. Если же все функции в классе являются виртуальными, то возникает ситуация, когда объект содержит в себе информацию о методах его обработки. В этом случае он становится «самодостаточным», имеет единственный параметр для доступа – указатель на него, а если к тому же это указатель на базовый класс, то мы можем даже не знать о его конкретной природе.

    Внешний полиморфизм как средство объединения классов Наличие общего базового класса позволяет объединить в общую структуру данных объекты различной природы, приведя указатели на них к этому классу. Наличие в нем виртуальной функции позволяет организовать регулярный процесс обработки данных, между которыми, в принципе, может и не быть ничего общего, кроме самого факта объединения и наличия общей функции обработки.

    Наполнение общего базового класса может быть различным в зависимости от наличия у производных классов общего функционального наполнения:

    • TEXT
         производные классы представляют собой вариации одной общей сущности, их общее функциональное наполнение может быть вынесено в базовый класс.
    • TEXT
         производные классы не имеют никакой общей основы в виде данных и алгоритмов. Тогда виртуальные функции в базовом классе играют роль интерфейса (в терминах Java), позволяющего подключать объекты этого класса к общей системе обработки, требующей от них поддержки определенного обобщенного свойства.

    Рассмотрим пример базового класса, имеющего общее наполнение. Базовый класс абстрактного графического объекта содержит данные – координаты центра и размеры охватывающего прямоугольника, а также ряд методов, оперирующих с этими данными: сохранение и загрузка из потока, определение пересечений и вложенности и т.п.. Некоторые объекты производных классов (например, точка или окружность) могут быть «вписаны» в эти данные (т.е. не иметь собственных) и частично использовать функции базового класса.

    //-----------------------------------------------114-01.cpp

    // Базовый класс графических объектов

    class GR{

    protected:

    TEXT
            int x,y,dx,dy;                 // Координаты центра и прямоугольник

    public:

    TEXT
            GR(int x0,int y0, int dx0, int dy0){
    
                        x=x0; y=y0;dx=dx0; dy=dy0;
    
                        }

    virtual ~GR(){} // Виртуальный деструктор !!!!!

    virtual int inside(GR &T){ // Проверка – находится внутри

    TEXT
                        return x-dx<=T.x-T.dx
    
                                    && x+dx>=T.x+T.dx
    
                                    && y-dy<=T.y-T.dy
    
                                    && y+dy>=T.y+T.dy;
    
                        }

    virtual void paint(){};

    virtual int save(ostream &O){ // Вывод в поток

    TEXT
                        O << x << " " << y << " " << dx << " " << dy << endl;
    
                        return O.good(); }

    virtual int load(istream &O){ // Ввод из потока

    TEXT
                        O >> x >> y >> dx >> dy;
    
                        return O.good(); }

    };

    // Классы точки и окружности «вписываются» в базовый класс

    class point : public GR{

    public: point(int x0,int y0):GR(x0,y0,0,0){}

    TEXT
            void paint(){…}

    };

    class circle : public GR{

    public: circle(int x0,int y0, int r):GR(x0,y0,r,r){}

    TEXT
            void paint(){…}

    };

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

    Классы, которые используют собственные данные, должны обеспечивать корректность данных базового класса. Например, класс полигона, имея собственные динамические массивы координат, должен пересчитывать координаты центра и размеры охватывающего прямоугольника в базовом классе (SetGR).

    class polygon : public GR{

    TEXT
            int *xx,*yy,n;
    
            void SetGR(){     // Установка параметров базового класса
    
                        int xmin=xx[0],ymin=yy[0],xmax=xx[0],ymax=yy[0];
    
                        for (int i=0;i<n;i++){
    
                                    if (xx[i]>xmax) xmax=xx[i];
    
                                    if (xx[i]<xmin) xmin=xx[i];
    
                                    if (yy[i]>ymax) ymax=yy[i];
    
                                    if (yy[i]<ymin) ymin=yy[i];
    
                                    }
    
                        x=(xmax+xmin)/2;
    
                        dx=(xmax-xmin)/2;
    
                        y=(ymax+ymin)/2;
    
                        dy=(ymax-ymin)/2;
    
                        }

    public:

    TEXT
            polygon(int x0[],int y0[],int nn):GR(0,0,0,0){
    
                        n=nn;
    
                        xx=new int[n];
    
                        yy=new int[n];
    
                        for (int i=0;i<n;i++)
    
                                    { xx[i]=x0[i]; yy[i]=y0[i]; }
    
                        SetGR();                       // Конструктор –  параметры БК
    
                        }
    
            ~polygon(){ delete []xx; delete []yy; }
    
            void paint(){…}
    
            int save(ostream &O){
    
                        O << n << endl;
    
                        for (int i=0;i<n;i++)
    
                                    O << xx[i] << " " << yy[i] << endl;
    
                        return O.good();
    
                        }
    
            int load(istream &I){
    
                        delete []xx;
    
                        delete []yy;
    
                        I >> n;
    
                        xx=new int[n];
    
                        yy=new int[n];
    
                        for (int i=0;i<n;i++)
    
                                    I >> xx[i] >> yy[i];
    
                        SetGR();                       // Ввод – параметры БК
    
                        return I.good();
    
                        }

    };

    Если базовый класс используется только для порождения производных классов, то виртуальные функции в базовом классе могут быть «пустыми», поскольку никогда не будут вызваны для объекта базового класса. Определять тела этих функций не требуется. Базовый класс, в котором есть хотя бы одна такая функция, называется абстрактным.

    class base {

    public:

    virtual void set(){} // Непустая функция

    virtual print()=0; // Пустая функция – класс абстрактный

    virtual get() =0; // Пустая функция

    };

    В таблице виртуальных функций для абстрактного класса пустой функции соответствует NULL-указатель. Транслятор запрещает создавать в программе объекты такого класса.

    В следующий примере речь идет о «пустом» абстрактном базовом классе, состоящим исключительно из объявлений пустых виртуальных методов. Если мы хотим создать упорядоченную структуру данных для хранения объектов произвольного типа, то нам потребуется «пустой» базовый класс, как минимум, содержащий виртуальную функцию сравнения. Структура данных, содержащая ссылки на объекты базового класса, получит возможность упорядочивать объекты, не зная их природы. Развернутый пример интерфейсного класса для представления в памяти объектов произвольного типа см. в 12.6.

    //-----------------------------------------------114-02.cpp

    // Интерфейсный абстрактный класс упорядоченных данных

    class Data{

    public: // Набор «пустых» виртуальных функций

    virtual int cmp(Data *)=0; // Сравнение объектов

    virtual void in(istream &)=0; // Ввод из потока

    virtual void out(ostream &)=0; // Вывод в поток

    };

    // Класс целых с интерфейсом ввода/вывода/сравнения

    class INT : public Data{

    TEXT
            int val;

    public:

    TEXT
            INT(int v=0){ val=v; }

    virtual int cmp(Data p){ return val - ((INT)p)->val; }

    virtual void in(istream &I){ I >> val; }

    virtual void out(ostream &O){ O << val; }

    };

    // Класс списка упорядоченных данных

    class list{

    TEXT
            struct elem{                   // Элемент списка содержит
    
                        Data *pd;           // указатель на объект «пустого» базового класса
    
                        elem *next;
    
                        elem(Data *p){ next=NULL; pd=p; }
    
                        };
    
            elem *hd;

    public:

    TEXT
            list(){ hd=NULL; }
    
            void out(ostream &O){     // Вывод с использованием полиморфного метода
    
                        for (elem *p=hd; p!=NULL; p=p->next)
    
                                    { p->pd->out(O); O << endl; }
    
                        }
    
            void inssort(Data *p){      // Сравнение с использованием полиморфного метода
    
                        elem *q=new elem(p);
    
                        if (hd==NULL) { hd=q; return; }                 // Пустой список
    
                        if (p->cmp(hd->pd)<0){                           // Вставка перед первым
    
                                    q->next=hd; hd=q; return;
    
                                    }                                               // Вставка перед большим
    
                        for (elem *s=hd; s->next!=NULL; s=s->next)
    
                                    if (p->cmp(s->next->pd)<0) break;
    
                        q->next=s->next;                                   // Вставка после s
    
                        s->next=q;                                            // перед s->next
    
                        }
    
            };

    Для вставки в односвязный список используется технологический прием – уаазатель s ссылается на элемент списка перед проверяемым (сравнивается c s->next). Тогда вставка осуществляется после элемента, на который ссылается s.

    Внутренний полиморфизм как элемент «отложенного» программирования Второй вариант полиморфизма касается внутреннего программирования базового и производного класса, т.е. вызова полиморфного метода в программном коде базового класса. Пусть в базовом классе определена виртуальная функция G. Тогда в программном коде базового класса ее вызов G() по «короткому» имени в неявном виде происходит через указатель на объект этого класса, т.е. this->G(), что является в Си необходимым условием полиморфного вызова. Если же объект базового класса будет являться частью объекта производного класса, в котором будет переопределен одноименный метод, то вызов этого метода должен произойти в производном классе.

    Рис. 114.3. «Отложенное» программирование класса B в классе A

    Технологически этот эффект можно интерпретировать как «отложенное» программирование класса A в классе B. Предположим, что мы разрабатываем базовый класс, в котором программируем некоторое действие. Оно в целом касается внутреннего программирования класса и инициируется событиями, происходящими в этом классе. Однако мы хотим вынести часть программного кода, относящегося к выполнению этого метода, в производный класс, что мы и делаем, вызывая полиморфный метод G. Таким образом, мы откладываем реализацию части действий, предусмотренных в базовом классе, на более поздний срок, т.е. в производный класс.

    «Отложенное» программирование действительно имеет дело не только с разным временем написания кода, но и с разным временем его трансляции. К моменту реализации производного программный код базового класса может присутствовать в проекте во внутреннем представлении (в виде объектного модуля). Приведем примеры «отложенного» программирования:

    • TEXT
         нейтрализация ошибок, происходящих при выполнении кода базового класса. Элементом отложенного программирования является принятие решения о способе обработки возникшей ошибки: игнорирование, завершения программы, исправление данных и т.п.;
    • TEXT
         дополнительные условия, касающиеся возможности выполнения заданных действий в базовом классе. Производный класс сообщает базовому классу в наследуемом полиморфном методе о возможности выполнить указанное действие;
    • TEXT
         обработка событий в производном классе, которые «отлавливаются» внутренними механизмами базового класса. Этот прием является основой для реализации в Си++ библиотек классов оконных приложений, построенных на принципах событийного программирования. Когда программист создает собственное оконное приложение, он разрабатывает производный класс от базового класса окна. При этом в базовом классе имеются внутренние механизмы, «отлавливающие» и обрабатывающие все события, которые касаются этого оконного приложения. При их появлении базовый класс вызывает соответствующие полиморфные методы в производном классе. Таким образом, программирование производного класса заключается в написании набора обработчиков событий, естественно, в контексте производного класса.

    В качестве примера рассмотрим производный класс, принимающий решение о выполнении операции деления на 0 в базовом классе, в котором для этого случая предусмотрен метод on_error.

    //-----------------------------------------------114-03.cpp

    // Обработка ошибки в производном классе - деление на 0

    class REAL{

    TEXT
            double val;

    public: REAL(double v=0){ val=v; }

    virtual void on_error(REAL &TWO){}

    TEXT
            void set(double v){ val=v; }
    
            void div(REAL &R){
    
                        if (R.val==0)  
    
                                    on_error(R);     // «Отложенное» действие
    
                        val/=R.val;
    
                        }
    
            void show(){ printf("%lf\n",val); }

    };

    class MY_REAL : public REAL{

    public:

    TEXT
            MY_REAL(double v=0):REAL(v){}
    
            void on_error(REAL &TWO)
    
                        {                                   // Вопрос – что делать???
    
                        printf("0-exit,1-set,default-ignore\n");
    
                        switch (getch()){

    case '0': exit(0);

    TEXT
                        break;                           // Завершить программу

    case '1': TWO.set(0.000000001);

    TEXT
                        break;                           // Исправить - деление на "очень маленькое"
    
                        }                                   // Иначе - игнорировать
    
            }

    };

    Вопросы без ответов Содержательно сформулируйте функциональное наполнение базового и производных классов. Определите, где вызываются виртуальные функции, и какой числовой результат они формируют в переменных в соответствии с их «смыслом» в каждом классе.

    //-------------------------------------------------114-05.cpp

    //---------------------------------------------------1

    class A{ int val;

    public:

    TEXT
      A(int n=0) { val = n; }
    
      virtual int get() { return val; }
    
      virtual int operator++(){ int t=val++; return t; }
    
      };

    class INC : public A {

    public: INC(int n) : A(n+1) {}

    TEXT
      int get() { return A::get()+1; }
    
      };

    void main(){

    A a(5),c(6); INC b(7);

    A *p[]={&a, &b, &c, NULL};

    int x=0, y=0, i=0;

    for (i=0; p[i]!=NULL; i++) x += p[i]->get();

    for (i=0; p[i]!=NULL; i++) y += (*p[i])++;

    }

    //---------------------------------------------------2

    class A{ int val;

    public:

    TEXT
      A(int n=0) { val = n; }
    
      int get() { return val; }
    
      virtual A operator++(){ A t=*this; val++; return t; }
    
      };

    class INC : public A {

    public:INC(int n) : A(n+1) {}

    TEXT
      A operator++(){ A::operator++(); return *this; }
    
      };

    void main(){

    A a(5),c(6); INC b(7);

    A *p[]={&a, &b, &c, NULL};

    int s=0, i=0;

    for (i=0; p[i]!=NULL; i++) s += (*p[i])++.get();

    A x=a++;

    A y=b++;

    A z=((A)&b)++; }

    //---------------------------------------------------3

    class A{ int val;

    public:

    TEXT
      A(int n=0) { val = n; }
    
      virtual int get() { return val; }
    
      A operator++(){ A t=*this; val++; return t; }
    
      };

    class INC : public A {

    public:INC(int n) : A(n+1) {}

    TEXT
      int get() { return A::get()+1; }
    
      };

    class DEC : public A {

    public:DEC(int n) : A(n-1) {}

    TEXT
      int get() { return A::get()-1; }
    
      };

    void main(){

    A a(5); INC b(7); DEC c(6);

    A *p[]={&a, &b, &c, NULL};

    int s=0, i=0;

    for (i=0; p[i]!=NULL; i++) (*p[i])++;

    for (i=0; p[i]!=NULL; i++) s += p[i]->get();

    }

    //---------------------------------------------------4

    class A{

    protected:

    TEXT
      int val,cnt;

    public:

    TEXT
      A() { val=cnt=0; }
    
      virtual A &operator+(int n) { cnt++; return *this; }
    
      virtual operator int(){ return cnt; }
    
      };

    class B : public A {

    public: A &operator+(int n) { cnt++; val+=n; return *this; }

    TEXT
      };

    class C : public A {

    public: A &operator+(int n) {

    TEXT
         if (cnt++ ==0) val=n;
    
         else if (n>val) val=n;
    
         return *this; }
    
      };

    void main(){

    A a; B b; C c;

    A *p[]={&a,&b,&c,NULL};

    for (int i=0; p[i]!=NULL; i++) *p[i]+5+3+7;

    b+2; c+2+1; }

    //---------------------------------------------------5

    class A{

    protected:

    TEXT
      int val,cnt;

    public:

    TEXT
      A() { val=cnt=0; }
    
      virtual A &operator+(int n) { cnt++; return *this; }
    
      virtual operator int(){ return 0; }
    
      };

    class B : public A {

    public: virtual A &operator+(int n) { cnt++; val+=n; return *this; }

    TEXT
      operator int(){ return val/cnt; }
    
      };

    class C : public A {

    public: A &operator+(int n) {

    TEXT
                         if (cnt++ ==0) val=n;
    
         else if (n>val) val=n;
    
         return *this; }
    
      operator int(){ return val; }
    
      };

    void main(){

    A a; B b; C c;

    A *p[]={&a,&b,&c,NULL};

    int v[3];

    for (int i=0; p[i]!=NULL; i++) *p[i]+5+3+8;

    b+1+3; c+2;

    for (int i=0; p[i]!=NULL; i++) v[i]=*p[i];

    }

    //---------------------------------------------------6

    class A{

    public:

    TEXT
      virtual int operator[](int n) { return n; }
    
      virtual operator int(){ return 0; }
    
      };

    class B : public A {

    TEXT
      int *p;
    
      int sz;

    public: B(int A[], int n) { p=A; sz=n;}

    TEXT
      int operator[](int n) { return n>=sz ? 0 : p[n]; }
    
      operator int(){ return sz; }
    
      };

    class C : public A {

    TEXT
      char *str;

    public:

    TEXT
      C(char *s) { str=s; }
    
      int operator[](int n) { return str[n]-'0'; }
    
      operator int(){ return strlen(str); }
    
      };

    void main(){

    int G[10]={4,2,5,1,7,4,19,3,2,1};

    A a; B b1(G,10),b2(G,3); C c1("45"), c2("12");

    A *p[]={&a,&b1,&b2,&c1,&c2,NULL};

    int x=0, y, j, i;

    for (i=0; p[i]!=NULL; i++){

    TEXT
         int m=*p[i];
    
         for (j=0;j<m;j++){
    
            y = (*p[i])[j];
    
            if (y>x) x=y;
    
            }}}

    //---------------------------------------------------7

    class A{

    public:

    TEXT
      virtual int operator[](int n) { return n; }
    
      virtual operator int(){ return 0; }
    
      };

    class B : public A {

    TEXT
      int *p;
    
      int sz;

    public:

    TEXT
      B(int A[], int n) { p=A; sz=n;}
    
      int operator[](int n) { return n>=sz ? 0 : p[n]; }
    
      operator int(){ return sz; }
    
      };

    class C : public A {

    TEXT
      char *str;

    public:

    TEXT
      C(char *s) { str=s; }
    
      int operator[](int n) { return str[n]-'0'; }
    
      operator int(){ return strlen(str); }
    
      };

    void main(){

    int G[10]={3,2,1};

    A a; B b1(G,3),b2(G,2); C c1("45"), c2("12");

    A *p[]={&a,&c1,&c2,&b1,&b2,NULL};

    int x=0, j, i;

    for (i=0; p[i]!=NULL; i++){

    TEXT
         int m=*p[i];
    
         for (j=0;j<m;j++)
    
            x += (*p[i])[j];
    
         }}

    //---------------------------------------------------8

    class A{

    public:

    TEXT
      virtual int operator[](int n) { return n; }
    
      virtual operator int(){ return 0; }
    
      };

    class B : public A {

    TEXT
      int *p;
    
      int sz;

    public:

    TEXT
      B(int A[], int n) { p=A; sz=n;}
    
      int operator[](int n) { return n>=sz ? 0 : p[n]; }
    
      operator int(){ return sz; }
    
      };

    class C : public A {

    TEXT
      char *str;

    public:

    TEXT
      C(char *s) { str=s; }
    
      int operator[](int n) { return str[n]-'0'; }
    
      operator int(){ return strlen(str); }
    
      };

    void main(){

    int G[10]={10,9,8,7,6,5,4,3,2,1};

    A a; B b1(G,10),b2(G,3); C c1("4567"), c2("1234");

    A *p[]={&a,&c1,&c2,&b1,&b2,NULL};

    int x=0, y=0, z=0, i=0;

    for (i=0; p[i]!=NULL; i++){

    TEXT
         z += (*p[i])[0];
    
         x += (*p[i])[i];
    
         y += *p[i];
    
         }}

    //---------------------------------------------------9

    class A{

    public:

    TEXT
      virtual int operator[](int n) { return n; }
    
      virtual operator int(){ return 0; }
    
      };

    class B : public A {

    TEXT
      int val;

    public: B(int n) { val=n; }

    TEXT
      int operator[](int n) {
    
         int m=val;
    
         while (n--!=0) m=m/10;
    
         return m%10;   }
    
      operator int(){
    
         int n=0,m=val;
    
         while (m!=0) m=m/10,n++;
    
         return n; }
    
      };

    class C : public A {

    TEXT
      char *str;

    public: C(char *s) { str=s; }

    TEXT
      int operator[](int n) { return str[n]-'0'; }
    
      operator int(){ return strlen(str); }
    
      };

    void main(){

    A a; B b1(25),b2(36); C c1("4567"), c2("1234");

    A *p[]={&a,&b1,&b2,&c1,&c2,NULL};

    int x=0, y=0, z=0, i=0;

    for (i=0; p[i]!=NULL; i++){

    TEXT
         x += (*p[i])[0];
    
         y += *p[i];
    
         }}

    //---------------------------------------------------10

    class A{

    public:

    TEXT
      virtual int get() { return 1; }
    
      virtual operator int(){ return 0; }
    
      };

    class B : public A {

    TEXT
      int val;

    public: B(int n) { val=n; }

    TEXT
        int get() { return val; }
    
      operator int(){ return 1; }
    
      };

    class C : public A {

    TEXT
      char *str;

    public: C(char *s) { str=s; }

    TEXT
        int get() { return strlen(str); }
    
      operator int(){ return 2; }
    
      };

    void main(){

    A a; B b1(5),b2(6); C c1("abc"), c2("1234");

    A *p[]={&a,&b1,&b2,&c1,&c2,NULL};

    int x=0, y=0, i=0;

    for (i=0; p[i]!=NULL; i++){

    TEXT
         x += p[i]->get();
    
         y += *p[i];
    
         }}

    //---------------------------------------------------11

    class A{ int val;

    public:

    TEXT
      A(int n=0) { val = n; }
    
      virtual int get() { return val; }
    
      virtual int operator++(){ int t=val++; return t; }
    
      };

    class INC : public A {

    public: INC(int n) : A(n+1) {}

    TEXT
        int get() { return A::get()+1; }
    
      };

    A a(5),c(6); INC b(7);

    struct list {

    A *pd;

    list *next;

    } c1={&c,NULL}, b1={&b,&c1}, a1={&a,&b1}, *h=&a1;

    void main(){

    list *q;

    int x=0, y=0, i=0;

    for (q=h; q!=NULL; q=q->next)

    TEXT
         x += q->pd->get();

    for (q=h; q!=NULL; q=q->next)

    TEXT
         y += (*q->pd)++;

    }