МЕНЮ


Фестивали и конкурсы
Семинары
Издания
О МОДНТ
Приглашения
Поздравляем

НАУЧНЫЕ РАБОТЫ


  • Инновационный менеджмент
  • Инвестиции
  • ИГП
  • Земельное право
  • Журналистика
  • Жилищное право
  • Радиоэлектроника
  • Психология
  • Программирование и комп-ры
  • Предпринимательство
  • Право
  • Политология
  • Полиграфия
  • Педагогика
  • Оккультизм и уфология
  • Начертательная геометрия
  • Бухучет управленчучет
  • Биология
  • Бизнес-план
  • Безопасность жизнедеятельности
  • Банковское дело
  • АХД экпред финансы предприятий
  • Аудит
  • Ветеринария
  • Валютные отношения
  • Бухгалтерский учет и аудит
  • Ботаника и сельское хозяйство
  • Биржевое дело
  • Банковское дело
  • Астрономия
  • Архитектура
  • Арбитражный процесс
  • Безопасность жизнедеятельности
  • Административное право
  • Авиация и космонавтика
  • Кулинария
  • Наука и техника
  • Криминология
  • Криминалистика
  • Косметология
  • Коммуникации и связь
  • Кибернетика
  • Исторические личности
  • Информатика
  • Инвестиции
  • по Зоология
  • Журналистика
  • Карта сайта
  • Объективное программирование

    линейную программу, используя объекты уже существующих

    классов. Следование же технологии объектно-ориентированного

    программирования "до конца" предполагает, что прикладная программа

    представляет собой класс самого верхнего уровня, в ее выполнение

    - создание объекта этого класса или выполнение для него некоторой функции

    типа "run".

    Лекция 7. Виртуальные функции.

    -----------------------------

    7.1 Понятие виртуальной функции

    ------------------------------

    Достаточно часто программисту требуется создавать структуры данных,

    включающих в себя переменное число объектов различных типов. Для

    представления их в программах используются списки или массивы ссылок на

    эти объекты. Объекты разных классов имеют соответственно различные типы

    ссылок, а для хранения в массиве или списке требуется один тип ссылок.

    Для преодоления этого противоречия все эти классы объектов требуется

    сделать производными от одного и того же базового класса, а при записи в

    массив преобразовывать ссылку на объект производного класса в ссылку на

    объект базового.

    p[] A1

    +---+ -b---------¬

    ¦ --------------------->-a-------¬¦======== b::f()

    +---+ ¦L---------¦===¬

    ¦ ------------¬ L----------- ¦

    +---+ ¦ C1 ¦

    ¦ ----------¬ ¦ -c---------¬ ¦

    +---+ ¦ L-------->-a-------¬¦======== c::f()

    ¦ ¦L---------¦===¦

    ¦ L----------- ¦

    ¦ A1 ¦

    L---------->-a-------¬ ===¦==== a::f()

    L--------

    class a

    { ... void f(); };

    class b : public a

    { ... void f(); };

    class c : public a

    { ... void f(); };

    a A1;

    b B1;

    c C1;

    a *p[3]; // Массив ссылок на объекты БК

    p[0] = &B1; // Ссылки на объекты БК в

    p[1] = &C1; // объектах ПК

    p[2] = &A1;

    Однако при таком преобразовании типа "ссылка на объект ПК" к

    типу "ссылка на объект БК" происходит потеря информации о том,

    какой объект производного класса "окружает" доступный через ссылку объект

    базового класса. Поэтому вместо переопределенных функций в производных

    классах будут вызываться функции в базовом, то

    есть

    p[0]->f(); // Вызов a::f()

    p[1]->f(); // во всех случаях, хотя f()

    p[2]->f(); // переопределены

    Однако по логике поставленной задачи требуется, чтобы вызываемая

    функция соответствовала тому объекту, который реально находится под

    ссылкой. Наиболее просто это сделать так:

    - хранить в объекте базового класса идентификатор "окружающего" его

    производного класса;

    - в списке или таблице хранить ссылки на объект базового

    класса;

    - при вызове функции по ссылке на объект базового класса

    идентифицировать тип производного класса и явно вызывать для него

    переопределенную функцию;

    - идентификатор класса устанавливать при создании объекта ,

    то есть в его конструкторе.

    class a

    {

    public: int id; // Идентификатор класса

    void f();

    void newf(); // Новая функция f() с идентификацией ПК

    }

    a::a() // Конструкторы объектов

    { ...

    id = 0;

    }

    b::b()

    { ...

    id = 1;

    }

    c::c()

    { ...

    id = 2

    }

    void a::newf()

    {

    switch (id)

    {

    case 0: a::f(); break;

    case 1: b::f(); break;

    case 2: c::f(); break;

    }

    }

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

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

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

    Отсюда следует определение виртуальной функции. Виртуальная функция

    (ВФ) - это функция, определяемая в базовом и наследуемая или

    переопределяемая в производных классах. При вызове ее по ссылке на

    объект базового класса происходит вызов той функции, которая соответствует

    классу объекта, включающему в себя данный объект базового класса.

    Таким образом, если при преобразовании типа "ссылка на ПК" к типу

    "ссылка на БК" происходит потеря информации об объекте производного класса,

    то при вызове виртуальной функции происходит обратный процесс неявного

    восстановления типа объекта.

    Реализация механизма виртуальных функций заключается в создании

    компилятором таблицы адресов виртуальных функций (ссылок).

    Такая таблица создается для базового класса и для каждого включения

    базового класса в производный. В объекте базового класса создается

    дополнительный элемент - ссылка на таблицу адресов его виртуальных

    функций. Эта ссылка устанавливается конструктуром при создании объекта

    производного класса. При вызове виртуальной функции по ссылке на объект

    базового класса из объекта берется ссылка на таблицу функций и из

    нее берется адрес функции по фиксированному смещению. Ниже иллюстрируется

    реализация этого механизма (подчеркнуты элементы, создаваемые неявно

    компилятром).

    class A

    {

    ------> void (**ftable)(); // Ссылка на таблицу адресов

    // виртуальных функций

    public:

    virtual void x();

    virtual void y();

    virtual void z();

    A();

    ~A();

    };

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

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

    A::A()

    {

    ------> ftable = TableA; // Установка таблицы для класса А

    }

    class B : public A

    {

    public:

    void x();

    void z();

    B();

    ~B();

    };

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

    // в классе B

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

    ¦ L переопределяется в B

    B::B() L------ наследуется из A

    {

    --> ftable = TableB; // Установка таблицы для класса B

    }

    void main()

    {

    A* p; // Ссылка p базового класса A

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

    // ного класса B

    реализация

    p->z(); ------------------> (*(p->ftable[2]))();

    }

    p nnn TableB B::z()

    -----¬ -------->--B-----¬ ----->---------¬ --->----------¬

    ¦ ------ ftable¦--A---¬¦ ¦ 0+--------+ ¦ ¦ ¦

    L----- ¦¦ ------ 1+--------+ ¦ ¦ ¦

    ¦+-----+¦ 2¦ --------- L---------

    ¦¦ ¦¦ L--------

    7.2 Абстрактные классы

    ---------------------

    Если базовый класс используется только для порождения производных

    классов, то виртуальные функции в базовом классе могут

    быть "пустыми", поскольку никогда не будут вызваны для объекта

    базового класса. Такой базовый класс называется абстрактным. Виртуальные

    функции в определении класса обозначаются следующим образом:

    class base

    {

    public:

    virtual print() =0;

    virtual get() =0;

    }

    Естественно, что определять тела этих функций не требуется.

    7.3 Множественное наследование и виртуальные функции

    ---------------------------------------------------

    Множественным наследованием называется процесс создания производного

    класса из двух и более базовых. В этом случае производный класс наследует

    данные и функции всех своих базовых классов.

    Существенным для реализации множественного наследования является

    то, что адреса объектов второго и т.д. базовых классов не совпадают с

    адресом объекта производного и первого базового классов,

    то есть имеют фиксированные смещения относительно начала объекта:

    class d : public a,public b, public c { };

    d D1;

    pd = &D1; // #define db sizeof(a)

    pa = pd; // #define dc sizeof(a)+sizeof(b)

    pb = pd; // pb = (char*)pd + db

    pc = pd; // pc = (char*)pd + dc

    D1

    pd -------------------->-d---------¬

    pa --------------------->-a-------¬¦T T

    ¦¦ ¦¦¦ ¦ db = sizeof(a)

    ¦L---------¦¦ +

    pb --------------------->-b-------¬¦¦ dc = sizeof(a) + sizeof(b)

    ¦L---------¦¦

    pc --------------------->-c-------¬¦+

    ¦L---------¦

    ¦ ¦

    L----------

    Преобразование ссылки на объект производного класса к ссылке

    на объект базового класса требует добавления к указателю текущего

    объекта this соответствующего смещения (db,dc), обратное преобразование -

    вычитание этого же смещения. Такое действие выполняется

    компилятором, когда в объекте производного класса наследуется

    функция из второго и т.д. базового класса, например при определении в

    классе "b" функции "f()" и ее наследовании в классе "d" вызов D1.f() будет

    реализован следующим образом:

    this = &D1; // Адрес объекта производного класса

    this = (char*)this + db // Адрес объекта класса b в нем

    b::f(this); // Вызов функции в классе b со своим

    // объектом

    Рассмотрим особенности механизма виртуальных функций при

    множественном наследовании. Во-первых, на каждый базовый класс в

    производном классе создается своя таблица виртуальных функций (в

    нашем случае - для "a" в "d", для "b" в "d" и для "c" в "d").

    Во-вторых, если функция базового класса переопределена в производном, то

    при вызове виртуальной функции требуется преобразовать

    ссылку на объект базового класса в ссылку на объект производного,

    то есть для второго и т.д. базовых классов вычесть из this соответствующее

    смещение. Для этого транслятор включает соответствующий код,

    корректирующий значение this в виде "заплаты", передающей управление

    командой перехода к переопределяемой функции.

    class a

    {

    public: virtual void f();

    virtual void g();

    };

    class b

    {

    public: virtual void h();

    virtual void t();

    };

    class c : public a, public b

    { // f(),t() наследуются

    public: void g(); // g() переопределяется

    void h(); // h() переопределяется

    }

    a A1;

    b B1;

    c C1;

    pa = &A1;

    pb = &B1;

    pa->f(); // Вызов a::f()

    pb->h(); // Вызов b::h()

    pa = &C1;

    pb = &C1;

    pa->f(); // Вызов a::f()

    pa->g(); // Вызов c::g()

    pb->h(); // Вызов c::h()

    pb->t(); // Вызов b::t()

    Таблицы виртуальных функций для данного примера имеют вид:

    A1

    -a----¬ Таблица ВФ для "a"

    ¦ ------------>--------¬

    +-----+ ¦a::f() ¦

    L------ +-------+

    ¦a::g() ¦

    L------- B1

    -b----¬ Таблица ВФ для "b"

    ¦ ------------>--------¬

    +-----+ ¦b::h() ¦

    L------ +-------+

    ¦b::t() ¦

    L------- C1

    T --c-----¬ Таблица ВФ для "a" в "c"

    ¦ ¦--a---¬¦ --------¬

    db ¦ ¦¦ ----------->¦a::f() ¦

    ¦ ¦L------¦ +-------+

    + ¦--b---¬¦ ¦c::g() ¦

    ¦¦ -------¬ L------- ¦L------¦ ¦ Таблица ВФ для

    "b" в "c"

    ¦ ¦ ¦

    ¦ ¦ L--->--------¬ "Заплата" для c::h()

    L-------- ¦ xxx()----->--xxx()----------------¬

    +-------+ ¦ this=(char*)this - db¦

    ¦b::t() ¦ ¦ goto c::h ¦

    L-------- L----------------------

    Другим вариантом решения проблемы является хранение необходимых

    смещений в самих таблицах виртуальных функций.

    7.4. Виртуальные базовые классы

    ------------------------------

    В процессе иерархического определения производных классов

    может получиться, что в объект производного класса войдут

    несколько объектов базового класса, например

    class base {}

    class a : public base {}

    class b : public base {}

    class c : a, b {}

    В классе "c" присутствуют два объекта класса base. Для исключения

    такого дублирования объект базового класса должен быть

    объявлен виртуальным

    class a : virtual public base {}

    class b : virtual public base {}

    class c : public a, public b {}

    a A1;

    b B1;

    c C1;

    Объект обыкновенного базового класса располагается, как правило, в

    начале объекта производного класса и имеет фиксированное

    смещение. Если же базовый класс является виртуальным, то требуется его

    динамическое размещение. Тогда в объекте производного

    класса на соответствующем месте размещается не объект базового

    класса, а ссылка на него, которая устанавливается конструктором.

    Для вышеприведенного примера имеем

    A1 B1 C1

    --a------¬ --b-----¬ --c---------------¬

    ¦ ------¬ ¦ ------¬ ¦ --a-------¬ ¦

    +--------+ ¦ +-------+ ¦ ¦ ¦ -------¬ ¦

    ¦ ¦ ¦ ¦ ¦ ¦ ¦ +---------+ ¦ ¦

    ¦-base--¬s); // второй объект к классу строк

    } // (переход от БК к ПК)

    //-------------------------------------------------------char

    *string::NAME() // Возвращает имя класса строк

    { return("Строка"); }

    //-------------------------------------------------------base

    *string::COPY() // Создание копии объекта

    { // без копирования значения

    string *p = new string; //

    return(p); //

    }

    //-------------------------------------------------------string::operator

    long() // Преобразование к типу long {

    // возвращает длину строки

    return (sz);

    }

    //-------------------------------------------------------string::operator

    char*() // Преобразование к типу char* {

    // возвращает текстовое представchar *p = new char[sz]; // ление

    значения объекта

    strcpy(p,s);

    return(p);

    }

    //--------------------------------------------------------base&

    string::operator+(char* two) // Операция "+ строка"

    { // Конкатенация строки в объекте

    char ss[80]; // и входной строки

    strcpy(ss,s); //

    strcat(ss,two);

    delete s;

    s = new char[sz = strlen(ss)+1];

    strcpy(s,ss);

    return(*(base*)this); // Возвратить неявную ссылку на объект

    } // вложенного базового класса

    //-------------------------------------------------------

    Базовый класс "base" необходим исключительно для обеспечения

    идентичного доступа к любому элементу базы данных независимо от

    его класса. Это абстрактный класс, содержащий объявление всех

    вышеперечисленных функций и операций виртуальными.

    class base

    {

    public:

    virtual int GET()=0; // Ввод значения объекта

    virtual void PUT()=0; // Вывод значения объекта

    virtual int CMP(base*)=0; // Сравнение значений объектов

    virtual char *NAME()=0; // Возвращает имя класса

    virtual base *COPY()=0; // Возвращает копию объекта

    virtual operator long()=0; // Преобразование к типу long

    virtual operator char*()=0; // Преобразование к типу char*

    virtual base& operator+(char*)=0;

    // Операция "+ строка"

    virtual ~base(); // Виртуальный деструктор для

    // разрушения объекта ПК по

    }; // ссылке на БК

    Сама двумерная таблица объектов организована традиционным

    для структур переменной размерности способом:

    - элемент БД создается в динамической памяти при добавлении

    строки к БД;

    - строка БД представлена массивом ссылок на объекты класса

    base. Сам массив также создается в динамической памяти при добавлении новой

    строки в БД;

    - ссылки на строки собраны в массив, который создается

    конструктором базы данных и заполняется при вызове функции добавления

    строки (таблица строк БД);

    - объект класса БД (table) содержит ссылку TBL на таблицу

    строк.

    Особо следует остановиться на способе назначения столбцам

    типов содержащихся в них элементов БД (или классов объектов). Это

    делается при помощи строки заголовка БД - head. Этот массив содержит

    ссылки на объекты, классы которых идентифицируют типы элементов в

    соответствующих столбцах. При создании новой строки БД

    виртуальной функцией COPY создаются копии объектов из строки заголовка БД,

    для которых затем вызывается виртуальная функция ввода значений GET.

    Строка заголовка создается конструктором объекта класса БД.

    Имеется меню типов элементов, которое представляет собой массив

    ссылок (TYPE) на объекты классов string,integer,dat и т.д.. Экранное меню

    строится при помощи вызова виртуальной функции вывода

    имени класса TYPE[i]->NAME(). После выбора строки меню ссылка на

    соответствующий выбранный объект переносится в строку заголовка БД.

    class table

    {

    int nc; // Количество столбцов

    int nr; // Количество строк

    char **names; // Имена стробцов

    base **head; // Строка объектов заголовка БД

    // для объявления типов объектов

    base ***TBL; // Таблица строк БД

    public:

    void append(); // Добавление строки в БД

    void sort(int); // Сортировка по значениям столбца

    long averrage(int); // Подсчет среднего арифметического

    // для столбца

    base& operator()(int,int);

    // Выбор объекта из БД

    table(); // Конструктор - создание БД

    ~table(); // Деструктор - удаление БД

    }

    объект БД

    TBL Массив строк БД

    --¬ ---------¬0

    ¦------->+--------+.. Элемент БД

    L-- +--------+i Строка БД string

    base*** ¦ ----------->---------¬0 integer

    +--------+ +--------+.. real

    +--------+ +--------+j --dat-------¬

    base** ¦ -------------->-base-----¬¦

    +--------+ ¦L----------¦

    base* ¦ ¦

    L-----------

    base

    head Строка заголовка БД

    --¬ S0

    ¦-------------->---------¬0 -string---¬

    L-- ¦ ------------------>-base---¬¦

    base** +--------+ ---------->L--------¦

    ¦ --------------¬ L--------- +-------

    -+ ¦ ¦ D0

    ¦ --------- ¦ -dat------¬

    +--------+ L--->-base---¬¦

    base* ¦L--------¦

    L---------//----------------------

    --------------------------------// Меню классов объектов (типов

    столбцов)

    string S0;

    dat D0;

    time T0;

    integer I0;

    base *TYPE[] = {

    (base*) &S0;

    (base*) &D0;

    (base*) &T0;

    (base*) &I0;

    };

    //-----------------------------------------------------// Создание

    структуры БД

    #define MAXCOL 30

    #define MAXREC 1000

    table::table()

    {

    int i,j,n;

    char ss[80];

    names = new char*[MAXCOL]; // Таблица адресов имен столбцов

    head = new base*[MAXCOL]; // Таблица ссылок на объекты

    for (nc=0; ncNAME() );

    }

    //------ выбор типа столбца - n

    head[nc] = TYPE[n]; // Ссылка на объект с классом,

    // соответствующим классу

    // объектов столбца

    TBL = new base**[MAXREC];

    nr = 0; // Таблица ссылок на строки БД

    }

    }

    //------------------------------------------------------// Деструктор

    БД

    tabe::~table()

    {

    int i,j;

    for (i=0; iCOPY();

    printf("Столбец %s типа %s :",names[i],head[i]->NAME());

    // Вывод подсказки имени и типа столбца

    while(TBL[nr][i]->GET() ==0);// Ввод значения нового объекта

    }

    nr++;

    }

    //-------------------------------------------------------// Нахождение

    среднего арифметического по заданному столбцу

    long table::averrage(int n)

    int i;

    //-------------------------------------------------------// Сортировка

    по заданному столбцу методом "пузырька"

    void table::sort(int n)

    {

    int i,k;

    base *p;

    do

    {

    for (i=0; i< nr-1; i++) // Виртуальная функция сравнения

    // объектов в соседних строках

    // n-го столбца

    if (TBL[i][n]->CMP(TBL[i+1][n]) class vector

    {

    int tsize; // Общее количество элеметов

    int csize; // Текущее количество элементов

    T **obj; // Массив ссылок на параметризован

    // ные объекты типа "T"

    public:

    T *operator[](int);

    // оператор [int] возвращает ссылку

    // на параметризованный объект

    // класса "T"

    void insert(T*); // функция включения объекта типа "T"

    int extract(T*); //

    };

    Данный шаблон может использоваться для порождения

    объектов-векторов, каждый из которых хранит объекты определенного типа. Имя

    класса при этом составляется из имени

    шаблона "vector" и имени типа данных (класса), который подставляется вместо

    параметра "Т":

    vector a;

    vector b;

    extern class time;

    vector c;

    Заметим, что транслятором при определении каждого

    вектора с новым типом объектов генерируется описание нового

    класса по заданному шаблону (естественно, неявно в процессе

    трансляции):

    class vector

    {

    int tsize;

    int csize;

    int **obj;

    public:

    int *operator[](int);

    void insert(int*);

    int index(int*);

    };

    Далее следует очевидное утверждение, что элементыфункции шаблона

    также должны быть параметризованы, то есть

    генерироваться для каждого нового типа данных. Действительно, это так:

    элементы-функции шаблона классов в свою

    очередь также являются шаблонными функциями с тем же самым

    параметром. То же самое касается переопределяемых операторов:

    --- параметр шаблона - класс "T", внутренний

    ¦ тип данных

    ¦ --- имя элемента-функции или

    ¦ ¦ оператора - параметризовано

    ¦ ¦

    template T* vector::operator[](int n)

    {

    if (n >=tsize) return(NULL);

    return (obj[n]);

    }

    template int vector::index(T *pobj)

    {

    int n;

    for (n=0; n::operator[](int n)

    {

    if (n >=tsize) return(NULL);

    return (obj[n]);

    }

    int vector::index(int *pobj)

    {

    int n;

    for (n=0; n class FIFO

    {

    int fst,lst; // Указатели на начало-конец

    // очереди

    T queue[size]; // Массив объектов класса "T"

    // размерности "size"

    public:

    T from(); // Функции включения-исключения

    void into(T); //

    FIFO(); // Конструктор

    };

    template FIFO::FIFO()

    {

    fst = lst = 0;

    }

    template T FIFO::from()

    {

    T work;

    if (fst !=lst)

    {

    work = area[lst++];

    lst = lst % size;

    }

    return(work);

    }

    template void FIFO::into(T obj)

    {

    area[fst++] = obj;

    fst = fst % size;

    }

    Пример использования:

    FIFO a;

    FIFO b;

    struct x {};

    FIFO c;

    Пример сгенерированного компилятором класса для объекта "a".

    class FIFO

    {

    int fst,lst;

    double queue[100];

    public:

    double from();

    void into(double);

    FIFO();

    };

    FIFO::FIFO()

    {

    fst = lst = 0;

    }

    double FIFO::from()

    {

    double work;

    if (fst !=lst)

    {

    work = area[lst++];

    lst = lst % 100;

    }

    return(work);

    }

    void FIFO::into(double obj)

    {

    area[fst++] = obj;

    fst = fst % 100;

    }

    Страницы: 1, 2, 3, 4, 5


    Приглашения

    09.12.2013 - 16.12.2013

    Международный конкурс хореографического искусства в рамках Международного фестиваля искусств «РОЖДЕСТВЕНСКАЯ АНДОРРА»

    09.12.2013 - 16.12.2013

    Международный конкурс хорового искусства в АНДОРРЕ «РОЖДЕСТВЕНСКАЯ АНДОРРА»




    Copyright © 2012 г.
    При использовании материалов - ссылка на сайт обязательна.