Лекции.ИНФО


Класс, который включает виртуальную функцию, называется полиморфным классом. Этот термин также применяется к классу, который наследует базовый класс, содержащий виртуальную функцию.



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

// Пример использования виртуальных функций.

#include <iostream>

using namespace std;

class base {

 public:

  virtual void who() {

   // объявление виртуальной функции

   cout << "Базовый класс.";

  }

};

class first_d : public base {

 public:

  void who() {

   // Переопределение функции who() для

   // класса first_d.

   cout << "Первый производный класс.";

  }

};

class second_d : public base {

 public:

  void who() {

   // Переопределение функции who() для

   // класса second_d.

   cout << "Второй производный класс.";

  }

};

Int main()

{

 base base_obj;

 base *p;

 first_d first_obj;

 second_d second_obj;

 p = &base_obj;

 p->who(); // доступ к функции who() класса base

 p = &first_obj;

 p->who(); // доступ к функции who() класса first_d

 p = &second_obj;

 p->who(); // доступ к функции who() класса second_d

 return 0;

}

При выполнении эта программа генерирует такие результаты.

Базовый класс.

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

Второй производный класс.

Теперь рассмотрим код этой программы подробно, чтобы понять, как она работает.

В классе base функция who() объявлена виртуальной. Это означает, что ее можно переопределить в производном классе (в классе, выведенном из base). И она действительно переопределяется в обоих производных классах first_d и second_d. В функции main() объявляются четыре переменные: base_obj (объект типа base), p (указатель на объект класса base), а также два объекта first_obj и second_obj двух производных классов first_d и second_d соответственно. Затем указателю p присваивается адрес объекта base_obj, и вызывается функция who(). Поскольку функция who() объявлена виртуальной, C++ во время выполнения программы определяет, к какой именно версии функции who() здесь нужно обратиться, причем решение принимается путем анализа типа объекта, адресуемого указателем p. В данном случае р указывает на объект типа base, поэтому сначала выполняется та версия функции who(), которая объявлена в классе base. Затем указателю р присваивается адрес объекта first_obj. Вспомните, что с помощью указателя на базовый класс можно обращаться к объекту любого его производного класса. Поэтому, когда функция who() вызывается во второй раз, C++ снова выясняет тип объекта, адресуемого указателем р, и, исходя из этого типа, определяет, какую версию функции who() нужно вызвать. Поскольку р здесь указывает на объект типа first_d, то выполняется версия функции who(), определенная в классе first_d. Аналогично после присвоения р адреса объекта second_obj вызывается версия функции who(), объявленная в классе second_d.

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

Виртуальную функцию можно вызывать обычным способом (не через указатель), используя оператор "точка" и задавая имя вызывающего объекта. Это означает, что в предыдущем примере было бы синтаксически корректно обратиться к функции who() с помощью следующей инструкции:

first_obj.who();

Однако при вызове виртуальной функции таким способом игнорируются ее полиморфные атрибуты. И только при обращении к виртуальной функции через указатель на базовый класс достигается динамический полиморфизм.

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

Поначалу может показаться, что переопределение виртуальной функции в производном классе представляет собой специальную форму перегрузки функций. Но это не так. В действительности мы имеем дело с двумя принципиально разными процессами. Прежде всего, версии перегруженной функции должны отличаться друг от друга типом и/или количеством параметров, в то время как тип и количество параметров у версий переопределенной функции должны в точности совпадать. И в самом деле, прототипы виртуальной функции и ее переопределений должны быть абсолютно одинаковыми. Если прототипы будут различными, то такая функция будет попросту считаться перегруженной, и ее "виртуальная сущность" утратится. Кроме того, виртуальная функция должна быть членом класса, для которого она определяется, а не его "другом". Но в то же время виртуальная функция может быть "другом" другого класса. И еще: функциям деструкторов разрешается быть виртуальными, а функциям конструкторов — нет.

Наследование виртуальных функций

Атрибут virtual передается "по наследству".

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

// Этот класс выведен из класса first_d, а не из base.

class second_d : public first_d {

 public:

  void who() {

   // Переопределение функции who() для класса second_d.

   cout << "Второй производный класс.";

  }

};

Если производный класс не переопределяет виртуальную функцию, то используется функция, определенная в базовом классе. Например, проверим, как поведет себя версия предыдущей программы, если в классе second_d не будет переопределена функция who().

#include <iostream>

using namespace std;

class base {

 public:

  virtual void who() {

   cout << "Базовый класс.";

  }

};

class first_d : public base {

 public:

  void who() {

   cout << "Первый производный класс.";

  }

};

class second_d : public base {

 // Функция who() здесь не определена вообще.

};

Int main()

{

 base base_obj;

 base *p;

 first_d first_obj;

 second_d second_obj;

 p = &base_obj;

 p->who(); // доступ к функции who() класса base

 р = &first_obj;

 p->who(); // доступ к функции who() класса first_d

 р = &second_obj;

 p->who(); /* Здесь выполняется обращение к функции who() класса base, поскольку в классе second_d она не переопределена. */

 return 0;

}

Теперь при выполнении этой программы на экран выводится следующее.

Базовый класс.

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

Базовый класс.

Как подтверждают результаты выполнения этой программы, поскольку функция who() не переопределена классом second_d, то при ее вызове с помощью инструкции p->who() (когда член р указывает на объект second_obj) выполняется та версия функции who(), которая определена в классе base.

Следует иметь в виду, что наследуемые свойства спецификатора virtual являются иерархическими. Поэтому, если предыдущий пример изменить так, чтобы класс second_d был выведен из класса first_d, а не из класса base, то при обращении к функции who() через объект типа second_d будет вызвана та ее версия, которая объявлена в классе first_d, поскольку этот класс является "ближайшим" (по иерархическим "меркам") к классу second_d, а не функция who() из тела класса base. Эти иерархические зависимости демонстрируются на примере следующей программы.

#include <iostream>

using namespace std;

class base {

 public:

  virtual void who() {

   cout << "Базовый класс.";

  }

};

class first_d : public base {

 public:

  void who() {

   cout << "Первый производный класс.";

  }

};

// Класс second_d теперь выведен из класса first_d, а не из класса base.

class second_d : public first_d {

 // Функция who() не определена.

};

Int main()

{

 base base_obj;

 base *p;

 first_d first_obj;

 second_d second_obj;

 р = &base_obj;

 p->who(); // доступ к функции who() класса base

 р = &first_obj;

 p->who(); // доступ к функции who() класса first_d

 р = &second_obj;

 p->who(); /* Здесь выполняется обращение к функции who() класса first_d, поскольку в классе second_d она не переопределена. */

 return 0;

}

Эта программа генерирует такие результаты.

Базовый класс.

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

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

Как видите, класс second_d теперь использует версию функции who(), которая определена в классе first_d, поскольку она ближе всех в иерархической цепочке классов.

Зачем нужны виртуальные функции

Как отмечалось в начале этой главы, виртуальные функции в сочетании с производными типами позволяют C++ поддерживать динамический полиморфизм. Полиморфизм существенен для объектно-ориентированного программирования по одной важной причине: он обеспечивает возможность некоторому обобщенному классу определять функции, которые будут использовать все производные от него классы, причем производный класс может определить собственную реализацию некоторых или всех этих функций. Иногда эта идея выражается следующим образом: базовый класс диктует общий интерфейс, который будет иметь любой объект, выведенный из этого класса, но позволяет при этом производному классу определить метод, используемый для реализации этого интерфейса. Вот почему для описания полиморфизма часто используется фраза "один интерфейс, множество методов".

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

Теперь у вас может возникнуть вопрос: почему же так важен общий интерфейс со множеством реализаций? Ответ снова возвращает нас к основной побудительной причине возникновения объектно-ориентированного программирования: такой интерфейс позволяет программисту справляться со все возрастающей сложностью программ. Например, если корректно разработать программу, то можно быть уверенным в том, что ко всем объектам, выведенным из базового класса, можно будет получить доступ единым (общим для всех) способом, несмотря на то, что конкретные действия у одного производного класса могут отличаться от действий у другого. Это означает, что программисту придется помнить только один интерфейс, а не великое их множество. Кроме того, производный класс волен использовать любые или все функции, предоставленные базовым классом. Другими словами, разработчику производного класса не нужно заново изобретать элементы, уже имеющиеся в базовом классе. Более того, отделение интерфейса от реализации позволяет создавать библиотеки классов, написанием которых могут заниматься сторонние организации. Корректно реализованные библиотеки должны предоставлять общий интерфейс, который программист может использовать для выведения классов в соответствии со своими конкретными потребностями. Например, как библиотека базовых классов Microsoft (Microsoft Foundation Classes — MFC), так и более новая библиотека классов .NET Framework Windows Forms поддерживают Windows-программирование. Использование этих классов позволяет писать программы, которые могут унаследовать множество функций, нужных любой Windows-программе. Вам понадобится лишь добавить в нее средства, уникальные для вашего приложения. Это — большое подспорье при программировании сложных систем.

Простое приложение виртуальных функций

Чтобы вы могли получить представление о силе принципа "один интерфейс, множество методов", рассмотрим следующую короткую программу. Она создает базовый класс figure, предназначенный для хранения размеров различных двумерных объектов и вычисления их площадей. Функция set_dim() является стандартной функцией-членом, поскольку эта операция подходит для всех производных классов. Однако функция show_area() объявлена как виртуальная, так как методы вычисления площади различных объектов будут разными. Программа использует базовый класс figure для выведения двух специальных классов rectangle и triangle.

#include <iostream>

using namespace std;

class figure {

 protected:

  double x, y;

 public:

  void set_dim(double i, double j) {

   x = i;

   у = j;

  }

  virtual void show_area() {

   cout << "Для этого класса выражение вычисления ";

   cout << "площади не определено.";

  }

};

class triangle : public figure {

 public:

  void show_area() {

   cout << "Треугольник с высотой ";

   cout << x << " и основанием " << у;

   cout << " имеет площадь ";

   cout << х * 0.5 * у << ".";

  }

};

class rectangle : public figure {

 public:

  void show_area() {

   cout << "Прямоугольник с размерами ";

   cout << x << " x " << у;

   cout << " имеет площадь ";

   cout << х * у << ".";

  }

};

Int main()

{

 figure *р; // создаем указатель на базовый тип

 triangle t; // создаем объекты производных типов

 rectangle r;

 р = &t;

 p->set_dim(10.0, 5.0);

 p->show_area();

 р = &r;

 p->set_dim(10.0, 5.0);

 p->show_area();

 return 0;

}









Читайте также:

  1. B. 1. В США говорят по-английски. 2. Эта сумка сделана из кожи. 3. Окно разбито. 4. Владимир был построен в 10 веке. 5. Масло и сыр делают из молока. 6.Этот дом был построен моим дедом.
  2. D. Правоспособность иностранцев. - Ограничения в отношении землевладения. - Двоякий смысл своего и чужого в немецкой терминологии. - Приобретение прав гражданства русскими подданными в Финляндии
  3. I. Определите значение терминов, сгруппируйте их по темам.
  4. IV. ТЕРМИНОЛОГИЧЕСКИЙ МИНИМУМ
  5. А Мастер засмеялся, смехом, который может случиться только с тем, кто пришел. Он сказал: «А ты думаешь, Я знаю?».
  6. А также занятий со спортивной направленностью
  7. А также на геологических картах и вертикальных разрезах
  8. Алфавитный указатель рекламных англоязычных терминов
  9. Англо-русский терминологический словарь
  10. Базовый комплекс приемов рукопашного боя (РБ-1)
  11. Безусловно, этот метод применим и к текстовым файлам.
  12. Биосфера – арена жизни живых существ, а также жизни и хозяйственной деятельности человека.


Последнее изменение этой страницы: 2016-03-17; Просмотров: 132;


lektsia.info 2017 год. Все права принадлежат их авторам! Главная