Как работает полиморфизм при работе с массивами дочерних объектов?
Мне нужно, чтобы объекты класов child и child2 , к примеру находились в одном массиве. Если я создам массив элементов base , я смогу обращаться к ним через интерфейс? Или обязательно создавать массив объектов interface ? Т. е. если я укажу тип данных base для массива, компилятор все равно выделит память под полный тип данных объекта включая interface ? Или нет?
Отслеживать
задан 24 фев 2018 в 15:53
107 1 1 серебряный знак 8 8 бронзовых знаков
Массив может содержать только элементы одного типа, поэтому объекты класов child и child2 не могут находиться в одном массиве.
24 фев 2018 в 15:57
Насколько мне известно в массиве элементов базового класса могут храниться объекты дочерних классов, полиморфизм как-ни-как))
24 фев 2018 в 16:05
Полиморфизм тут не при чем, и в массиве элементов базового класса объекты других типов храниться не могут.
24 фев 2018 в 16:06
Оно компилируется, но в массиве будут только объекты базового типа. А компилируется оно потому, что вы забыли запретить конструкторы и операторы копирования / перемещения. base obj; child cobj; obj = cobj; у вас тоже будет компилироваться, но в переменной obj по-прежнему будет объект типа base . Инициализация и присвоение не могут изменить тип объекта. А код можно добавить отредактировав вопрос.
24 фев 2018 в 16:29
Нет, это потому что массив является гомогенным контейнером, то есть по определению содержит только элементы одного типа. Аналогичный гетерогенный контейнер — это кортеж. При этом у обоих тип всех элементов зафиксирован во время компиляции.
24 фев 2018 в 16:41
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Если вы создадите массив объектов base , то, как вы понимаете, каждому объекту будет выделена память только под base , и вы получите при присваивании срезку.
Используйте массив указателей на base — и все получится 🙂
Еще — непонятно, зачем вы делаете child производным и от base , и от interface , при том, что interface является производным от base . Не то чтобы это было запрещено. но это очень специфичное решение, которое вряд ли вам необходимо, скорее, вы немного недоразобрались в вопросе.
Да, и override пишется с другой стороны объявления 🙂
Отслеживать
ответ дан 24 фев 2018 в 15:56
219k 15 15 золотых знаков 120 120 серебряных знаков 230 230 бронзовых знаков
Ахахах. Ну да, я еще разбираюсь. Мне нужно сделать так, потому что в коде уже все написано для объектов класса base , а интерфейс я только сейчас решил добавить) Много правок придется сделать. Аааа. Т. е. они автоматически наследуются, я понял))) Короче я сам запутался, но главное вы мне сказали, спасибо)
C++ для начинающих Виртуальные функции Продолжение знакомства Динамический полиморфизм
В продолжении темы начала знакомства с виртуальными функциями пишу этот материал. Несмотря на то, что я описал первопричину использования виртуальных функций, этого явно мало. В литературе для лучшего понимания виртуальных функций используют пример, в котором создается массив объектов. Это не обычный массив объектов, а каждый элемент массива имеет объект различного типа. Вспомним что такое класс. Класс – это пользовательский тип данных. Значит каждый новый класс это новый тип.
Давайте вспомним одно свойство массива. Массив – это набор однотипных данных. Может показаться, что использование наследования и виртуальных функций немного нарушает такое определение. Если размышлять, то можно прийти к мыслям: “Если массив содержит данные только одного типа, то значит в один массив объекты разных типов мне поместить не удастся”. Это совершенно верные мысли. Несмотря на это любой самый обычный массив можно построить таким образом, что элементы, обрабатываемые с помощью него будут иметь различные типы. Может показаться, что я противоречу сам себе, но вовсе нет. Для того, чтобы построить такой массив с элементами различных типов используют массив указателей. Каждый элемент такого массива – это указатель.
Перехожу от слов к делу.
Задача: Создать массив объектов класса млекопитающие
Предположим у нас есть 3 вида млекопитающих и остальные неизвестные нам. Нам известны собака, кот и свинья. Каждое млекопитающее издает собственные звуки. Кот мяукает, собака лает, свинья хрюкает.
Создадим базовый класс – млекопитающие, от которого унаследуем виды млекопитающих и определим для них уникальное поведение (как нам уже известно каждое млекопитающее должно издавать свой звук)
Код C++ Виртуальные функции Динамический полиморфизм
================
/*КЛАСС-РОДИТЕЛЬ*/
class Mammal
public:
virtual void Speak () //Виртуальный метод. Звук неизвестного млекопитающего
/*СОЗДАЕМ ПОДКЛАСС МЛЕКОПИТАЮЩЕГО*/
class Dog :public Mammal
public:
void Speak () //Виртуальный метод. Собака лает
>;
/*СОЗДАЕМ ПОДКЛАСС МЛЕКОПИТАЮЩЕГО*/
class Cat :public Mammal
public:
void Speak () //Виртуальный метод. Кот мяукает
>;
/*СОЗДАЕМ ПОДКЛАСС МЛЕКОПИТАЮЩЕГО*/
class Pig :public Mammal
public:
void Speak () //Виртуальный метод. Свинья хрюкае т
>;
================
Применены приемы наследования. Создано три потомка из основного класса. Каждый потомок – это известное нам млекопитающее
После того как описали нужные действия приступаем к написанию основного кода. Дадим пользователю возможность заносить млекопитающих в массив в том порядке и тех млекопитающих, которых он сам хочет.
Код С++ Виртульные функции Динамический полиморфизм
================
#include
#include
/*КЛАСС-РОДИТЕЛЬ*/
class Mammal
public:
virtual void Speak () < cout / /Виртуальный метод. Звук неизвестного млекопитающего
>;
/*СОЗДАЕМ ПОДКЛАСС МЛЕКОПИТАЮЩЕГО*/
class Dog :public Mammal
public:
void Speak () //Виртуальный метод. Собака лает
>;
/*СОЗДАЕМ ПОДКЛАСС МЛЕКОПИТАЮЩЕГО*/
class Cat :public Mammal
public:
void Speak () //Виртуальный метод. Кот мяукает
>;
/*СОЗДАЕМ ПОДКЛАСС МЛЕКОПИТАЮЩЕГО*/
class Pig :public Mammal
public:
void Speak () //Виртуальный метод. Свинья хрюкает
>;
/*==================*/
void main ()
system ( “CLS” );
Mammal * Array [ 5 ]; //Объявляем массив указателей на класс Млекопитающие
Mammal * ptr ; //Объявили указатель на класс Млекопитающие
int MyWibor ; //Переменная для выбора пользователем
//Небольшое украшательство
cout cout cout cout
//С помощью цикла заполняем массив указателями на объекты
for (int i = 0 ; i < 5 ; i ++)//Пусть млекопитающих пять
cout //Номер текущего млекопитающего
cin >> MyWibor ; //Пользователь вводит число
switch ( MyWibor ) //Основываясь на введенном числе выделяем память для нужного млекопитающего
case 1 :
ptr =new Dog ; //Если один – выделяем память для создания класса Собака
break;
case 2 :
ptr =new Cat ; //Если два – выделяем память для создания класса Кошка
break;
case 3 :
ptr =new Pig ; //Если три – выделяем память для создания класса Свинья
break;
default:
ptr =new Mammal ; //Если что-то другое – выделяем память для создания класса Млекопитающее
break ;
> //switch Выбор сделан
Array [ i ]= ptr ; //Выбор сделан, память выделена, заносим указатель на созданный объект в массив
> //Цикл for завершен
for ( i = 0 ; i < 5 ; i ++) Array [ i ]->Speak (); //Проходим по всему циклу и вызываем метод Speak для каждого элемента
for ( i = 0 ; i < 5 ; i ++) delete Array [ i ]; //Мы выделяли память, значит нужно её освободить.
cin . get ();
cin . get ();
>
================
При запуске этой программы будет предложено ввести число. В зависимости от введенного числа программа определит объект какого типа пользователь стремиться создать. Для объекта выбранного типа выделится необходимая память и указатель, который указывает на адрес созданного объекта будет записан в массив как элемент массива. Благодаря использованию виртуальных функций нам нет нужды задумываться о том, как правильно вызвать метод, который будет описывать звук издаваемый млекопитающим. Другими словами для каждого элемента массива будет вызван именно тот метод, который описан или переопределен в объекте, на который указатель, являющийся элементом массива указывает
Посмотрев и поняв работу примера вы лучше поймете описание полиморфизма: Один класс, множество методов
Основная идея использования полиморфизма : “Независимо от возрастания объемов кода и сложности программы, ко всем объектам, выведенным из базового класса, можно использовать один единственный общий способ доступа для каждого объекта, независимо от того что поведения этих объектов различны”
Полиморфизм
Полиморфизм – это возможность для объектов разных классов, связанных с помощью наследования, реагировать различным способом при обращении к одной и той же функции-элементу. Это помогает создавать универсальные механизмы, описывающие поведение не только базового класса, но и классов-потомков.
Продолжим разработку базового класса CShape, в котором определим функцию-член GetArea(), предназначенную для расчета площади фигуры. Во всех классах-потомках, произведенных наследованием от базового класса, мы переопределим эту функцию в соответствие с правилами расчета площади конкретной фигуры.
Для квадрата (класс CSquare) площадь вычисляется через стороны, для круга (класс CCircle) площадь выражается через радиус и так далее. Мы можем создать массив для хранения объектов типа CShape, в котором можно будет хранить как объект базового класса, так и всех его потомков. В дальнейшем мы можем вызывать одну и ту же функцию для любого элемента данного массива.
//— Базовый класс
class CShape
<
protected :
int m_type; // тип фигуры
int m_xpos; // X — координата точки привязки
int m_ypos; // Y — координата точки привязки
public :
void CShape(); // конструктор, тип равен нулю
int GetType()< return (m_type);>; // возвращает тип фигуры
virtual
double GetArea() < return ( 0 ); >// возвращает площадь фигуры
>;
Теперь все производные классы имеют функцию-член getArea(), которая возвращает нулевое значение. Реализация этой функции в каждом потомке будет отличаться.
//— производный класс Круг
class CCircle : public CShape // после двоеточия указывается базовый класс,
< // от которого производится наследование
private :
double m_radius; // радиус круга
public :
void CCircle(); // конструктор, тип равен 1
void SetRadius( double r);
virtual double GetArea() < return ( 3 . 14 *m_radius*m_radius);>// площадь круга
>;
Для квадрата объявление класса выглядит аналогично:
//— производный класс Квадрат
class CSquare : public CShape // после двоеточия указывается базовый класс,
< // от которого производится наследование
private :
double m_square_side; // сторона квадрата
public :
void CSquare(); // конструктор, тип равен 2
void SetSide( double s);
virtual double GetArea() < return (m_square_side*m_square_side);>//площадь квадрата
>;
Так как для вычисления площади квадрата и круга требуются соответствующие значения членов m_radius и m_square_side, то в объявлении соответствующего класса мы добавили функции SetRadius() и SetSide().
Предполагается, что в программе у нас используются объекты разного типа (CCircle и CSquare), но унаследованные от одного базового типа CShape. Полиморфизм позволяет нам создать массив объектов базового типа CShape, но при объявлении этого массива сами объекты еще неизвестны и их тип неопределен.
Решение о том, объект какого типа будет содержаться в каждом элементе массива, будет приниматься непосредственно при выполнении программы. Это подразумевает динамическое создание объектов соответствующих классов, и, следовательно, необходимость вместо самих объектов использовать указатели объектов.
Для динамического создания объектов используется оператор new, каждый такой объект нужно самостоятельно и явно удалять оператором delete. Поэтому мы объявим массив указателей типа CShape и для каждого его элемента создадим объект нужного типа ( new Имя_класса) , как это показано в примере скрипта:
//+——————————————————————+
//| Script program start function |
//+——————————————————————+
void OnStart ()
<
//— объявим массив указателей объектов базового типа
CShape *shapes[5]; // массив указателей на объекты CShape
//— здесь заполняем массив производными объектами
//— объявим указатель на объект типа CCircle
CCircle *circle= new CCircle();
//— задаем свойства объекта по указателю circle
circle.SetRadius(2.5);
//— поместим в shapes[0] значение указателя
shapes[0]=circle;
//— создаем еще один объект CCircle и запишем его указатель в shapes[1]
circle= new CCircle();
shapes[1]=circle;
circle.SetRadius(5);
//— тут мы намеренно «забыли» задать значение для shapes[2]
//circle=new CCircle();
//circle.SetRadius(10);
//shapes[2]=circle;
//— для неиспользуемого элемента установим значение NULL
shapes[2]= NULL ;
//— создаем объект CSquare и запишем его указатель в shapes[3]
CSquare *square= new CSquare();
square.SetSide(5);
shapes[3]=square;
//— создаем объект CSquare и запишем его указатель в shapes[4]
square= new CSquare();
square.SetSide(10);
shapes[4]=square;
//— массив указателей есть, получим его размер
int total= ArraySize (shapes);
//— пройдем в цикле по всем указателям в массиве
for ( int i=0; i <5;i++)
<
//— если по указанному индексу указатель является валидным
if ( CheckPointer (shapes[i])!= POINTER_INVALID )
<
//— выведем в лог тип и площадь фигуры
PrintFormat ( «Объект типа %d имеет площадь %G» ,
shapes[i].GetType(),
shapes[i].GetArea());
>
//— если указатель имеет тип POINTER_INVALID
else
<
//— сообщим об ошибке
PrintFormat ( «Объект shapes[%d] не инициализирован! Его указатель %s» ,
i, EnumToString ( CheckPointer (shapes[i])));
>
>
//— мы должны самостоятельно уничтожить все созданные динамические объекты
for ( int i=0;i
//— удалять можно только объекты, чей указатель имеет тип POINTER_DYNAMIC
if ( CheckPointer (shapes[i])== POINTER_DYNAMIC )
<
//— сообщим об удалении
PrintFormat ( «Удаляем shapes[%d]» ,i);
//— уничтожим объект по его указателю
delete shapes[i];
>
>
>
Обратите внимание, что при уничтожении объекта оператором delete необходимо проверить тип его указателя. Удалять с помощью delete можно только объекты, имеющие указатель POINTER_DYNAMIC, для указателей другого типа будет получена ошибка.
Кроме переопределения функции при наследовании, полиморфизм включает в себя также и реализацию одной и той же функции с разным набором параметров в пределах одного класса. Это означает, что в классе может быть определено несколько функций с одним и тем же именем, но с разным типом и/или набором параметров. В этом случае полиморфизм реализуется через перегрузку функций.
Полиморфизм в C++ и как правильно его реализовать с использованием указателя на void?
Описание вопроса объемное, но сам он скорее всего простой. Суть его такова:
Имеем класс интерфейс, базовый класс, два класса-наследника базового. В интерфейсе также имеем виртуальную ф-ю вывода информации, которая «уточняется» в классах-наследниках.
//класс интерфейс class InterfaceClass < public: InterfaceClass(); ~InterfaceClass(); virtual void output() = 0; >//базовый class Base : public InterfaceClass< public: //тут нужные конструкторы void output(); //выводим a protected: int a; >; //производный class DerivateOne : public Base< public: //тут конструкторы void output(); //выводим b1 и a protected: int b1; >; //производный class DerivateTwo : public Base< public: //тут конструкторы void output(); //выводим b2 и a protected: int b2; >
Далее мы хотим создать массив из объектов типа DerivateOne и DerivateTwo непосредственно в main():
. DerivateOne ArrOne[3]; //массив типа DerivateOne DerivateTwo ArrTwo[3]; //массив типа DerivateTwo //инициализируем элементы массива .
И, собственно, сам вопрос. Пусть у нас есть указатель на void:
void *ptr;
Каким образом пройти по массиву, используя этот самый указатель и адресную арифметику, а именно инкремент (ptr++)?
То бишь вместо:
for (int i = 0; i < 3; i++)< ArrOne[i].output();
Писать что-то в стиле:
ptr = &ArrOne[0] for (int i = 0; i < 3; i++)< ptr->output(); ptr++; >
И, соответственно, после этого ptr будет указывать на элементы массива ArrTwo.
Нужно ли привести void *ptr к типу интерфейса (InterfaceClass*), а уже затем станут доступны операции вида ptr++ и подобные действия с выводом?
Спасибо, что дочитали=)
- Вопрос задан более трёх лет назад
- 2491 просмотр