Глава 19
При обычном способе программирования написание программ может затягиваться на
длительное время. Например, если вы создаете процедуру, то не можете использовать ее в дальнейшем, поскольку новая процедура должна чем-то слегка отличаться от этой.
Или, допустим, вы создаете процедуру, которая отлично работает в каких-то ситуациях, но есть и другие случаи, когда эта процедура должна работать несколько иначе. Как обычно выходят из такого положения? Копируют коды созданной процедуры, вставляют их в новое место, редактируют и затем присваивают этой процедуре новое имя. Таким образом, вы как бы повторно используете написанные коды, однако данный метод не лишен недостатков. Копируя и вставляя коды, вы не только раздуваете размеры программы, но и способствуете размножению ошибок, ведь если ошибка содержалась в скопированном коде, она многократно проявится во всех частях программы, куда этот код будет вставлен.
Объектно-ориентированное программирование позволяет избежать подобных проблем.
Вы можете просто взять существующий фрагмент кодов, наследовать его и внести все необходимые модификации. И никакого копирования— вы просто повторно используете то, что уже создано и работает. Эта возможность называется наследованием кодов.
Вверх
Возможность наследования становится особенно полезной при построении объектов.
Этот класс можно наследовать, чтобы создать новый класс, представляющий политика. Затем созданный класс можно снова наследовать, чтобы получить новый класс, представляющий честного политика. (Хорошо, потребность в последнем классе у вас вряд ли когда-нибудь появится, но в данном случае мы просто рассматриваем общую идею.) Все новые объекты, созданные таким образом, могут наследовать свойства тех объектов, из которых они были получены, а также могут иметь модифицированные или добавленные возможности.
Кроме того, если вы обнаружите и исправите ошибку в базовом классе, эта же ошибка автоматически будет исправлена и для всех производных классов. Другими словами, если вы увидите, что в классе, представляющем политика, засела какая-то ошибка, исправьте ее, и она автоматически будет также исправлена в классе, представляющем честного политика.
Чтобы воспользоваться возможностью наследования, при объявлении нового класса после его имени наберите двоеточие (:) и затем укажите название класса, коды которого будут наследоваться.
Все функции-члены базового класса (он также называется родительским) становятся
Функциональность всех добавленных элементов будет отличать новый класс от базового.
В классе представлены основные характеристики, присущие каждому политику. Класс,
Поскольку этот класс является производным от класса Политик, процедуры
Вверх
Зачем делать то, что уже сделано?
Нужно приложить некоторые усилия и проявить настойчивость, чтобы научиться извлекать все выгоды из возможности повторного использования кодов. Как уже упоминалось в предыдущей главе, чем больше вы программируете, тем профессиональнее вы это делаете. Ниже приведено несколько общих рекомендаций
Совет:
Вверх
Рано или поздно вам придется обратить внимание на те различия, которые возникают при наследовании членов классов с разными уровнями доступа. Вот правила, регулирующие этот процесс.
(В действительности в языке C++ есть еще несколько дополнительных правил, которые касаются наследования открытых, закрытых и защищенных членов класса. О них речь идет в разделе "Защищенное и закрытое наследование" ниже в этой главе.)
Вверх
При необходимости можно изменить поведение элементов, которые были наследованы.
Предположим, например, что у вас есть класс, представляющий фигуры. Он может выглядеть приблизительно так:
Если теперь вы хотите создать отдельный класс, представляющий линии, можете наследовать его из класса ShapeObject, изменив порядок действий, выполняемых функцией Draw (воспользовавшись возможностью перегрузки):
Созданный таким образом класс LineObject будет иметь те же свойства и характеристики, что и класс ShapeObject, за исключением функции Draw, работа которой будет отличаться от работы функции Draw класса ShapeObject.
Вверх
Если вы воспользовались возможностью перегрузки и изменили работу наследованных
функций-членов, то иногда все же может возникать необходимость доступа к соответствующим функциям-членам базового класса. (Как дети иногда нуждаются в помощи родителей, так и производные классы обращаются к базовым с тем, чтобы получить дополнительные возможности. Может быть, именно поэтому базовые классы иногда называют родительскими.) Сделать это несложно. Просто наберите имя базового класса, затем два двоеточия (::) и название нужной функции.
Предположим, например, что функции Draw класса LineObject необходимо вызвать
Это полезная возможность, поскольку если вам необходимо получить функциональность, которая имеется у базового класса, вам не нужно копировать его коды и вставлять их в производный класс. Вместо этого можно просто вызвать нужную функцию родительского класса и затем написать дополнительные коды для выполнения оставшейся работы.
Вверх
Здесь приведен код небольшой программы, на примере которого вы можете увидеть, как происходит наследование и перегрузка кодов. В программе есть два класса: базовый, выполняющий одну работу, и производный, работающий несколько иначе. Кроме того, вы можете увидеть, как функция производного класса вызывает функцию базового класса.
//Базовый класс. В нем содержится информация о цене и функция,
//Функция базового класса, отображающая цену на экране
//Производный класс, в котором функция PrintMe будет
//Функция производного класса, которая вызывает функцию
//Ожидание, пока пользователь не остановит выполнение
//С этой строки начинается выполнение программы
oBaseCiass.nPrice = 1;
Console::WriteLine(S"Вызов функции базового класса");
Console::WriteLine(S"3ызов функции производного класса");
Как же процесс наследования отображается на конструкторах
По мере написания более сложных программ вы начнете создавать классы, которые имеют в своем арсенале несколько конструкторов. В этом разделе рассматривается, как вызывать такие конструкторы для классов, полученных путем наследования. Уделите данному вопросу должное внимание, и в будущем вы сможете оценить эффективность этого приема.
Когда вы создаете объект производного класса, вызывается конструктор базового класса.При этом компилятор в первую очередь ищет конструктор, установленный по умолчанию (тот, который не имеет параметров).
Например:
В данном случае вызов конструктора NewPen (int, int) сопровождается вызовом
Вверх
Если у нас есть указатель, ссылающийся на объект базового класса, его же можно использовать и для указания на объект производных классов. Предположим, например, что . . . является указателем на обьект класса ShapeObject :
Если какой-то объект является производным от класса ShapeObject, то для ссылки на него так же можно использовать указатель poShape::
«Ну и что?» - спросите вы, а мы ответим: "Это очень важно, поскольку отсюда следует, что вы можете использовать один и тот же указатель для ссылки на множество различных объектов. Так, вам не обязательно заранее знать, какой объект создаст пользователь данного класса: специальный, производный или еще какой-то. Вы должны использовать один и тот же указатель, и он будет работать с производными объектами так же хорошо, как и с базовыми".
Предположим, например, что вы пишете программу, которая может рисовать различные
Увидеть этот прием в действии вы сможете ниже в -этой главе на примере программы
Поскольку классы LineObject и CircleObject являются производными от класса
Почему указатель может ссылаться на объекты разных классов?
Как вы знаете, C++ следит за корректностью использования типов данных. Например, если у вас есть указатель типа int*, то он не может ссылаться на значение типа float . Однако в случае с производными классами все обстоит несколько иначе.
Когда компилятор создает производный класс, вначале в нем размещаются элементы, которые наследуются из базового класса. Например, если первые четыре элемента базового класса имели тип integer, то в производном классе первые четыре элемента также будут иметь тип integer.
Если компилятор имеет дело с указателем на базовый класс, то он знает, как в нем найти различные члены данных и функции-члены по их местоположению в этом классе.
Если вы используете тот же указатель для ссылки на производный класс, то все члены данных и функции-члены, наследованные от базового класса, будет так же легко найти, поскольку производный класс выглядит как копия базового класса с некоторыми незначительными изменениями. Вот почему у вас есть такая возможность. Компилятор сможет найти любую функцию по ее местоположению в базовом классе, несмотря на различия, внесенные в производный класс в результате наследования.
А что произойдет, если в производном классе будет перегружена какая-либо функция базового класса? Об этом речь пойдет ниже.
Защищенное и закрытое наследование
В табл 19.1 показано, какие права доступа приобретают элементы производного класса при использовании в процессе наследования ключевых слов public, private и
Таблица 19.1. Влияние слов public, private и protected на процесс наследования
Что происходит при наследовании кодов
Предположим, например, что у вас есть класс, представляющий информацию о человеке.
ПроизводныйКласс : БазевыйКласс
функциями-членами производного класса (которым называется также дочерним). То же самое касается и членов данных. Таким образом, вам не нужно заново набирать для них коды. Если вы хотите добавить в новый класс какие-то элементы, перечислите их при его объявлении.
Вот например, объявление класса, представляющего политика:
Класс Политик
{
БратьДеньги()
представляющиий мифического честного политика, может быть создан приблизительно так:
класс ЧестныйПолитик : public Политик
{
ДаватьОбещания() и БратьДеньги() являются его составной частью. Но в дополнение к этому он содержит в себе также метод ГоворитьПравлу. Ничего сложного, не так ли?
которые следует помнить при разработке объектно-ориентированных программ.
Помните, что нужен большой практический опыт, чтобы научиться создавать качественные объектно-ориентированные программы. Если что-то не получается, многое приходится переделывать заново; при этом, разумеется, возникает масса ошибок. Однако не огорчайтесь — все это является частью образовательного процесса.
собственноручно. С тем же успехом можно наследовать любые встроенные .NET-
классы. Например, вы можете набрать код. подобный этому:
class MyPen : public Pen
Наследование открытых, закрытых и защищенных элементов
пределах производного класса, так и за его пределами.
граммы (в том числе и для производного класса),
класса, но невидимы для всей остальной программы.
Перегрузка функций-членов
Чтобы сделать это, заново объявите для производного класса функции-члены, работа которых должна отличаться от работы тех же функций-членов базового класса. При этом сами имена функций-членов не меняются. Эта возможность называется перегрузкой
(overriding) и является одним из самых мощных и часто используемых средств языка C++.
__gc class ShapeObject
{
void PrintStats();
__gc class LineObject : public ShapeObject
{
Родительские связи
функцию Draw класса ShapeObject. Вот как это делается:
void LineObject::Draw(Graphics *poG)
{
ShapeObject::Draw(poG);
//Создание пера для рисования
Pen *poPen = new Pen(Color::Red);
//Рисование линии
poG->DrawLine(poPen, m_nXFrom, m_nYFrom, m_nXTo, m_nYTo);
А теперь немного практики
//Наследование
#include "stdafх.h"
#using <mscorlib.dll>
using namespace System;
//отображающая значение цены на экране
class Base
{
int nPrice;
void PrintMe();
void Base::PrintMe()
{
//определена по-своему
class Derived : public Base
{
//базового класса
void Derived::PrintMe()
{
//Вызов функции базового класса
Base::PrintMe
();
//программы
void HangOut()
{
выполнение программы");
Console::ReadLine();
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
Derived oDerivedClass;
oDerivedClass.nPrice = 7;
cBaseClass.PrintMe();
oDerivedClass.PrintMe
();
HangCut ();
return 0;
и деструкторах
Иногда возникает необходимость в создании специализированных конструкторов. Например вы уже могли видеть, что объект класса Реn может создаваться с указанием цвета либо с указанием цвета и ширины пера. Если на основе класса Реn вы создадите производный класс, то наверняка при создании объектов нового класса вам захочется использовать параметризованные конструкторы базового. Сделать это можно с помощью конструктора производного класса. Для этого просто укажите конструктор базового класса, который должен вызываться при вызове конструктора производного класса, и список используемых параметров:
Например:
Derived::Derived():base(....);
NewPen::NewPen(int Width, int Style) : Pen(Color::Red, nWidth);
Конструктора базового о класса Pen, который устанавливает цвет и ширину пера.
Универсальный указатель
LineObject *poShape;
poShape = new LineObject();
фигуры, среди которых могут быть круги и линии. Вы можете создать базовый класс
ShapeObject и производные классы: LineObject для рисования линий и CircleObject
для рисования кругов. Далее для хранения информации об отображаемых фигурах вы можете создать список ArrayList и использовать для ссылки на любой из его элементов указатель на объект класса ShapeObject .
Draw8. В список ArrayList будут добавляться объекты LineObject и CircleObject. a
затем для доступа к ним вы просто используете такой код (ну хорошо, слово просто в данном случае, пожалуй, является не самым подходящим):
static_cast
ShapeObject и поскольку класс ShapeObject имеет функцию-член Draw, этот код будет работать независимо от того, является ли текущий элемент списка объектом класса LineObject или объектом класса CircleObject.
производный класс, мы использовали только ключевое слово public. Кроме него, можно использовать также ключевые слова private и protected.
protected.
Используемое слово | Члены базового класса объявлены как ... | Члены производного класса наследуются как ... |
public | public protected private |
public protected Недоступны для наследования |
protected | public protected protected |
public protected Недоступны для наследования |
private | public protected private |
private private Недоступны для наследования |
Вверх
Виртуальные функции используются тогда, когда указатель на объект иногда ссылается на базовый класс, а иногда — на производный. К счастью, все это не так сложно, как выглядит поначалу. Чуть выше, в разделе "Универсальный указатель", вы уже видели фрагмент кода из программы DrawS, где список ArrayList использовался для хранения объектов CircleObject и LineObject. Это, собственно, и все, о чем пойдет речь.
Конечно, здесь есть одна маленькая загвоздка. Предположим, что в производном классе вы перегрузили одну из функций-членов, изменив тем самым ее поведение. Например, в классе LineObject функция Draw была перегружена таким образом, чтобы в процессе ее выполнения на экране рисовались линии. А в классе CircleObject та же функция была изменена так, чтобы ее вызов сопровождался рисованием кругов. Теперь, если вы используете указатель типа ShapeObject для ссылки на один из этих объектов и вызовете функцию Draw, вы не получите тот результат, который вам нужен: будет вызвана функция Draw базового класса.
Вот что получается. Метод наследования предоставляет вам великолепную возможность повторного использования кодов, а свойства указателей обеспечивают гибкость при работе с объектами. Но, когда эти два неоспоримых преимущества накладываются друг на друга, возникает серьезная проблема.
И тут на помощь приходят виртуальные функции. Когда их видят указатели, они знают, что им придется вызывать перегруженные функции-члены производного класса. Если программисты говорят между собой о полиморфизме как о важнейшем преимуществе объектно-ориентированного программирования, на самом деле речь идет о виртуальных функциях.
Когда вы делаете функцию Draw виртуальной, упомянутая выше проблема исчезает. Если у вас есть объект LineObject, но на него ссылается указатель типа ShapeObject, вызов функции Draw будет означать вызов именно функции LineObject: : Draw, а не ShapeObject: : Draw. Более того, при этом не возникает никакой путаницы. Так, если вы ссылаетесь на объект LineObject с помощью указателя типа LineObject, то при вызове функции Draw также будет вызываться функция LineObject: : Draw. Другими словами, с виртуальными функциями происходят только правильные вещи.
Ответив на перечисленные ниже вопросы, вы сумеете определить, можно ли какую-то
Если ответы на все четыре вопроса будут положительными, вам нужна виртуальная функция.
Виртуальная функция объявляется в базовом, но не в производном классе. Для этого наберите перед названием функции ключевое слово virtual:
Этот код говорит о том, что в классе Base есть виртуальная функция PrintMe.
Теперь компилятор сможет корректно вызывать функцию PrintMe, если вы используете
Вам не обязательно набирать слово virtual перед названием функции при объявлении производного класса. Однако лучше это сделать, поскольку в дальнейшем при чтении кодов программы вам легче будет определить, что в данном случае вы имеете дело с виртуальной функцией.
Однако следующий код более понятен:
Если вы еще не вполне ясно представляете, для чего нужны виртуальные функции, просмотрите приведенный в этом разделе пример программы, где используются виртуальная и невиртуальная функции. Запустив эту программу на выполнение и увидев возвращаемый ею результат, вы поймете, почему при работе с указателями иногда нужно создавать виртуальные элементы.
В программе используются два класса: Bass и Derived. Класс 3ase содержит функ-
class Base
Класс Derived создается путем наследования класса Base. При этом обе функции перегружаются:
Работа функции main начинается с создания объекта класса Вазе. Чтобы продемонстрировать свойства виртуальной функции, для доступа к объекту используется указатель:
Далее создается объект класса Derived. Для ссылки на этот объект используется ранее объявленный указатель типа Base. И теперь самое интересное: функции-члены производного класса вызываются с помощью указателя, созданного для ссылки на объекты базового класса:
Когда этот код будет выполняться, вы увидите, что будет вызвана функция-член PrintMe класса Дажe несмотря на то, что указатель poBase в данный момент ссылается на объект класса Derived. Это происходит потому, что функция PrintMe не является виртуальной. В то же время вызванная функция PrintMeV будет относиться к классу Derived. Почему? Потому что она является виртуальной. (На рис. 19.1 показан результат выполнения приведенных выше кодов.)
. . .
Рис. 19.1. Использование виртуальных функций позволяет указателям находить функции-члены производных классов
После того как вы объявили какую-то функцию виртуальной, она становится таковой для всех производных классов. Например, функция PrintMeV была объявлена как виртуальная для класса Base. Следовательно, она также будет виртуальном и для производного класса Derived. Если вы создадите новый класс (назвав его, допустим, DerivedKiddo), производный от класса Derived, его функция PrintMeV также будет виртуальной. Независимо от того, как много классов будут наследовать коды других классов (а на количество классов, создаваемых путем наследования, ограничений нет), компилятор всегда сможет правильно определит - функцию какого класса вы хотите вызвать.
#include "stdafx.h"
//This is the base class.
//Print it, letting us know it is the base function
void Base::PrintMeV()
//Derived is inherited from Base.
//Let us know the derived one was called.
void Derived::PrintMeV()
//Hang out until the user is finished
// This is the entry point for this application
//Call the two functions
delete poBase;
//Now use the base class pointer to point to the derived class
//Call the two functions
HangOut();
Вверх
Обратите внимание, что в данном примере объекты самого класса ShapeObject никогда не будут сохраняться в списке ArrayList. Действительно, ведь нет некой общей фигуры, которую нужно будет отображать на экране. Отображать понадобится только заранее определенные фигуры, такие как круги или квадраты. Класс ShapeObject предназначен всего лишь для представления общей концепции геометрической фигуры.
Чтобы обобщить эту концепцию и не допустить непреднамеренного прямого использования класса ShapeObject, вы можете сделать его абстрактным базовым классом. Абстрактный базовый класс представляет некую концепцию (отсюда и его название — абстрактный), но сам не несет никакой функциональности. Вы никогда не сможете создать объект такого класса: если попытаетесь это сделать, компилятор сразу же выскажет все, что думает по этому поводу. Этот класс может быть использован только для создания на его основе производных классов. А сами производные классы должны определить функциональность для каждой функции-члена, наследованной от абстрактного базового класса.
Чтобы сообщить компилятору, что создаваемый класс должен быть абстрактным, нужно
Историческая справка: как раз тогда, когда я писал эти строки, у меня родилась дочь Габриэла.
Класс, функции-члены которого объявлены таким образом, не может иметь собственных объектов, т.е. вы не сможете создать объект класса ShapeObject, набрав код наподобие такого:
И это здорово! Ведь вам такие объекты и не нужны. Этот класс вам нужен только как базовый для создания серии производных классов. И уже при создании производных классов вы должны позаботиться о том, чтобы вдохнуть жизнь (т.е. определить функциональность) во все исключительно виртуальные функции. Если же вы этого не сделаете, компилятор напомнит вам о ваших обязанностях.
Классы к наследованию нужно подготовить. Вот небольшой список, с которым следует сверяться, чтобы определить, все ли сделано для того, чтобы какой-либо класс смог стать базовым (родительским) и произвести на свет парочку производных (дочерних) классов. (Готовы ли ползунки и погремушки? И как насчет детской коляски и кроватки?)
Теперь, когда вы уже имеете общее представление о виртуальных функциях и абстрактных базовых классах, пришло время применить эти замечательные возможности на практике и создать настоящую программу. Мы добавим функциональность программе рисования, чтобы ее выполнение действительно сопровождалось отображением на экране кругов и линий.
Как она будет работать? Как и ранее, для сохранения информации об отображаемых фигурах будет использоваться список ArrayList. Но теперь в этот список будут помещаться объекты классов LineObject и CircleObject:
//Отображаемое сообщение переводится так: "Наберите с, чтобы
Оба класса являются производными от абстрактного базового класса ShapeObject.
Как и ранее, при извлечении объектов из списка нужно указать, к какому классу они относятся. В данном случае вы указываете, что все объекты относятся к классу ShapeObject, и затем вызываете функции-члены, полагаясь на то, что компилятор сможет разобраться, к какому классу (LineObject или CircleObject) на самом деле относится текущий объект и какая функция должна быть вызвана:
При этом достигается действительно великолепная гибкость и функциональность. Попробуйте набрать коды приведенной ниже программы и запустить ее на выполнение.
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
//Stores shape information
//Stores line information
//Initialize values for this line object
//Draws the line
//Stores circle information
//Initialize values for this circle object
//Draws the line
//Stores all of the lines to draw
//Initialize the DisplayObject structures
//Add a new object
while (!fFinished)
if (pszMore->Equals(S"c"))
//Initialize it
//Should we add another one?
//Draw all of the shapes
// This is the entry point for this application
//Add the shapes
//Move the display surface to the far left
//Draw the shapes
//Free up the graphics surface
//Hang out until the user closes the form
Назад |
Начало урока |
Вверх |
Вперед
Виртуальная реальность
Тест на виртуальность
конкретную функцию сделать виртуальной. Если ответ хотя бы на один из вопросов будет отрицательным, делать функцию виртуальной нет необходимости.
станет ли он таким в будущем?)
функции в производном классе? (Будут ли они отличаться в будущем?)
класс? Другими словами, будут ли какие-то указатели ссылаться попеременно то
на базовый, то на производный классы.
Декларация ваших виртуальных намерений
class Base
{
int Price;
virtual void PrintMe();
Предположим, что на основе этого класса вы создаете производный:
class Derived : public Base
{
virtual void PrintMe();
один и тот же указатель для ссылки на объекты этих двух классов.
Например, класс Derived можно объявить так:
class Derived : public Base
{
void PrintMe();
class Derived : public Base
{
virtual void PrintMe();
Когда без этого не обойтись
цию-член PrintMe, которая не является виртуальной, и виртуальную функцию-член
PrintMeV:
{
void PrintMe f);
virtual void PrintMeV();
class Derived : public Base
{
void PrintMe();
virtual void PrintMeV();
Base *poBase = new Base
();
Затем с его помощью вызываются функции-члены:
poBase->PrintMe();
poBase->PrintMeV();
poBase = new Derived();
poBase->PrintMe() ;
poBase->PrintMeV();
Вот полная распечатка кодов программы, о которой только что шла речь:
// Inherit2
// Illustrates virtual functions
#using <mscorlib.dll>
using namespace System;
class Base
{
void PrintMe();
virtual void PrintMeV();
void Base::PrintMe()
{
{
class Derived : public Base
{
void PrintMe();
virtual void PrintMeV();
void Derived::PrintMe()
{
{
void HangOut()
{
Console::ReadLine();
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
Base *poBase = new Base();
Console::WriteLine(S"Using the base class");
poBase->PrintMe();
poBase->PrintMeV();
poBase = new Derived();
Console::WriteLine(S"Using the derived class");
poBase->PrintMe();
poBase->PrintMeV();
return 0;
Абстрагирование от деталей
все функции-члены этого класса сделать исключительно виртуальными. Этот термин означает, что интерфейс класса объявляется, но не определяется. Чисто технически это выполняется следующим образом: при объявлении функций-членов в конце строки наберите = 0;
//Объявление абстрактного базового класса
gc class ShapeObject
{
virtual void Draw(Graphics *poG) = 0;
ShapeObject foo;
Готов ли класс к тому, чтобы обзавестись потомством?
Искусство абстрагирования
Console::WriteLine("Туре с to create a circle and 1 to create a line " ) ;
//создать круг, и 1, чтобы создать линию"
pszMore = Console::ReadLine();
if (pszMore->Equals(S"c">)
{
poShape = new CircleObject();
else
i
//Создание объекта, представляющего линию
poShape = new LineObject();
}
И все эти классы имеют виртуальные функции-члены Initialize и Draw, которые поразному определяются для каждого производного класса. Так, для класса LineObject функции-члены с такими именами считывают значения координат двух точек и рисуют между ними прямую линию. Для класса CircleOoject такие же функции считывают координаты центра круга и длину радиуса и, исходя из этих данных, рисуют круг на экране.
IEnumerator *poEnumerator = m_paShapes->GetEnumerator();
while (poEnumerator->MoveNext())
{
static_cast
// Draw8
// Uses inheritance and virtual functions
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>
using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace System::Collections;
//An abstract base class
__gc class ShapeObject
{
virtual void Initialize() = 0;
virtual void Draw(Graphics *poG) = 0;
__gc class LineObject : public ShapeObject
{
virtual void Initialize();
virtual void Draw(Graphics *poG);
private:
int m_nXFrom;
int m_nYFrom;
int m_nXTo;
int m_nYTo;
void LineObject::Initialize()
{
Console::WriteLine(S"What is the starting x position?");
m_nXFrom = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the starting y position?");
m_nYFrom = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the ending x position?");
m_nXTo = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the ending y position?");
m_nYTo = Int32::Parse(Console::ReadLine());
void LineObject::Draw(Graphics *poG)
{
Pen *poPen = new Pen(Color::Red);
//Draw the line
poG->DrawLine(poPen, m_nXFrom, m_nYFrom, m_nXTo, m_nYTo);
__gc class CircleObject : public ShapeObject
{
virtual void Initialize();
virtual void Draw(Graphics *poG);
private:
int m_nXCenter;
int m_nYCenter;
int m_nRadius;
void CircleObject::Initialize()
{
Console::WriteLine(S"What is the center x position?");
m_nXCenter = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the center y position?");
m_nYCenter = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the radius?");
m_nRadius = Int32::Parse(Console::ReadLine());
void CircleObject::Draw(Graphics *poG)
{
Pen *poPen = new Pen(Color::Blue);
//Draw the line
poG->DrawEllipse(poPen, m_nXCenter-m_nRadius, m_nYCenter-m_nRadius, m_nXCenter+m_nRadius, m_nYCenter+m_nRadius);
__gc class DisplayObject
{
void Add();
void Draw(Graphics *poG);
DisplayObject();
private:
ArrayList *m_paShapes;
DisplayObject::DisplayObject()
{
void DisplayObject::Add()
{
String *pszMore;
ShapeObject *poShape;
{
Console::WriteLine("Type c to create a circle and l to create a line");
pszMore = Console::ReadLine();
{
poShape = new CircleObject();
else
{
poShape = new LineObject();
poShape->Initialize();
//Store it in the structure
m_paShapes->Add(poShape);
Console::WriteLine("Hit y to enter a new shape");
pszMore = Console::ReadLine();
if (!pszMore->Equals(S"y"))
{
void DisplayObject::Draw(Graphics *poG)
{
IEnumerator *poEnumerator = m_paShapes->GetEnumerator();
while (poEnumerator->MoveNext())
{
static_cast
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
Form *poForm = new Form();
Graphics *poGraphics = poForm->CreateGraphics();
DisplayObject *poDisplay = new DisplayObject();
poDisplay->Add();
poForm->SetDesktopLocation(0,0);
//Show the display surface
poForm->Show();
poDisplay->Draw(poGraphics);
poGraphics->Dispose();
Application::Run(poForm);
Содержание