Наследование классов
- Войдите на сайт для отправки комментариев
Чт, 27/08/2020 - 10:05
Подскажите как лучше реализовать и написать пример кода.
Хочу сделать класс, например Work (родительский класс), от которого будут наследоваться Teacher и Driver
у всех трех будут функции start() и end() выполняющие разный код.
создаю переменный Teacher t; Driver d; и указатель Work *w;
указателю присваиваю ссылку (в зависимости от условий) на t или d
и выполняю функции start() и end()
без использования классов реализовал это указателями на функцию,
но хочется сделать код красивее.
обяви в родительском классе абстрактные виртуальные функции start() и end(), а реализацию их напиши в override дочерних классах.
Вопщем, много читай.
делай так
Я немного отсебятины при печати добавил. А то пример совсем скучный будет.
выводит в порт
Спасибо!
а если вызвать функцию, которая объявлена как виртуальная в родительском, но нет в дочернем -
вызовет критическую ошибку при исполнении или пойдет на пустой цикл?
а если вызвать функцию, которая объявлена как виртуальная в родительском, но нет в дочернем -
вызовется родительская, если она не обьявлена как абстрактная. Если объявлена, то компилятор тебе не даст обьявить дочерний класс и не переопределить такую функцию.
обяви в родительском классе абстрактные виртуальные функции start() и end()
не обязательно абстрактные, главное чтоб виртуальные.
Спасибо!
а если вызвать функцию, которая объявлена как виртуальная в родительском, но нет в дочернем -
вызовет критическую ошибку при исполнении или пойдет на пустой цикл?
Не думай о виртуальных функциях как о чем то не знакомом тебе! Это все те же указатели на функцию, только красиво, как просил. И св-ва их такие же. Каждый потомок переопределяет их - всего лишь присваивает свой указатель на свою функцию. Если не переопределял - останется значение от последнего в иерархии присвоившего. Если указатель объявили но он 0 - абстрактный класс, вызов даст ошибку. Так что думай о виртуальных как о указателях на функцию и вопросов не будет. Но с виртуальными нельзя делать все что можно с указателям. Указатели на функцию куда более мощные, а значить и опасные. Потому их и "притупили" и в ООП обернули, чтоб недоумки реже ошибались.
тоже вопрос по наследованию, раз уж ТС начал
Допустим, я хочу создать свой класс Child, производный от какого-то стороннего класса Base., при этом хочу переопределить в новом классе метод print(). который, к сожалению, в базовом классе не описан как виртуальный.
В итоге имеем ситуацию, что когда метод print() вызывается методами класса Child - вызывается новый метод, а когда вызов происходит из методов класса Base - то и print() вызывается тоже "старый"
как иллюстрация - вышеприведенный код печатает:
Вопрос - можно ли решить проблему ( чтобы всегда вызывался метод Child::print()) без изменения класса Base?
что пробовал
попытка переопределить базовый метод при помощи using как приватный:
ни на что не влияет.
Попытка вовсе вычистить его с помощью delete не компилируется:
Нельзя.
нет
Допустим, я хочу создать свой класс Child, производный от какого-то стороннего класса Base., при этом хочу переопределить в новом классе метод print(). который, к сожалению, в базовом классе не описан как виртуальный.
В итоге имеем ситуацию, что когда метод print() вызывается методами класса Child - вызывается новый метод, а когда вызов происходит из методов класса Base - то и print() вызывается тоже "старый"
Вопрос - можно ли решить проблему ( чтобы всегда вызывался метод Child::print()) без изменения класса Base?
Разработчик базового класса решает, какой метод будет вызываться в случае наследования. Есть у него такое право или нет?
Как говорится: "это не бага, это - фича".
//Как говорится: "это не бага, это - фича".
Даже хуже. Это архитектура. Некоторые разработчики, особо сволочные ;) , еще и имеют наглость писать private. И это в исходниках на моем то компе! Но мы же не дети. Раз исходник найден - то private там или нет уже не важно )))
Вопрос - можно ли решить проблему ( чтобы всегда вызывался метод Child::print()) без изменения класса Base?
Нельзя. У тебя невиртуальные методы не попадают в VMT, которая как раз полиморфизм и обеспечивает. Так что невиртуальные функции - обычные методы класса с разной сигнатурой для родителя и дитяти.
Спасибо мужики
эээээ.....
а поговорить?
а поговорить?
да о чем тут говорить? :) - я и сам знаю, как это делать правильно - добавить в базовый класс спецификатор virtual для метода print()
Но в моем случае базовый класс - это чужая библиотека, и хотелось бы обойтись без ее модификации. Надеялся - а вдруг есть какой хитрый способ? :)
Тута, кроме virtual есть еще одна хитрость. Если у тебя в родительском классе есть виртуальная функция, например
virtual void TSensor::Error(int);
а в дочернем ты объявишь такую же функцию, но по невнимательности с другим типом параметра, например
void TAnalogSensor::Error(int8_t);
то компилятор это съест, но это будут две разные функции(из-за различающегося типа параметра) и они нихрена полиморфировать друг в друга не будут. Чтоб убедиться, что ты перекрываешь родительскую функцию, надо писать так:
void TAnalogSensor::Error(int); override;
тогда ты точно будешь знать, что перекрываешь именно функцию родителя, ибо, если ты ошибся с типом параметра, компилятор срауже будет материца.
Но в моем случае базовый класс - это чужая библиотека
модификация сводится к одному слову virtual, и перемещению этого обьявления в секцию protected (если она была в private). :)
модификация сводится к одному слову virtual, и перемещению этого обьявления в секцию protected (если она была в private). :)
любая модификация - да хоть на запятую - приводит к необходимости распространять кастомную версию библиотеки со своим кодом. И как об этом не пиши в описании - обязательно найдется юзер, который поставит стандартную либу с гитхаба и потом будет орать, что ничегго не работает :)
любая модификация - да хоть на запятую - приводит к необходимости распространять кастомную версию библиотеки со своим кодом. И как об этом не пиши в описании - обязательно найдется юзер, который поставит стандартную либу с гитхаба и потом будет орать, что ничегго не работает :)
Это как раз совсем не проблема. Вернее, проблема, конечно, но она не имеет отношения к модификации библиотеки от слова совсем.
В Вашей фразе слишком много слов. Если бы Вы написали просто "обязательно найдется юзер" и ничего больше - Вы бы обозначили истинную проблему, а не надуманную.
А истинная проблема в том. что что бы и как бы Вы ни делали (или "не делали"), как бы не извращались и чтобы не писали в описании, обязательно найдётся юзер!!! Поэтому, делайте спокойно как Вам надо и не парьтесь насчёт юзера - он всё равно найдётся по любому, и от Вас (Ваших действий) это зависит чуть менее, чем никак.
натолкнулся на интересный "глюк" компилятора (Visual Studio Code), чуть голову не сломал (ответ будет ниже, для тех кто захочет сам понять проблему)
код компилируется без ошибки:
при нажатии '#' происходило переключение на следующий пункт меню (cur_menu становилось равно 1), но тут же сбрасывалось в начало (0)
Решение:
#################################################
В завершении не стоит break
case 0: ;
Почему переменная сбрасывалась???
или вся программа уходила в начало и переменные инициализировались??
sturmber, скорее всего это, как часто бывает, не глюк компилятора, а глюк программиста. Но понять что происходит по куску кода невозможно. Если бы Вы привели полный пример (как можно более короткий, но чтобы проблема оставалась), можно было бы посмотреть и подумать.
Так-то на первый взгляд, всё логично. Если нажата "#", то cur_menu становится 1, а если потом не нажато ничего, то в строке №ХЗ выполняется --cur_menu и она снова становится нулём. А Вы чего-то другого ожидали?
(Кстати, о "строке №ХЗ" - ЗАДОЛБАЛИ!!! Вставляйте же, наконец код правильно!!!)
неожиданно, что компилятор пропустил без ошибки
думал, это обязательная структура - завершить case .. break
Читать надо больше
Читать надо больше
Именно!
sturmber - отсутствие break в case - это не баг, а фича, используемая для обьединения веток switch.
я правильно понимаю логику оператора switch..case:
программа сравнивает с первым case, если соответствует, то исполняет его и если доходит до ещё одного case, то опять сравнивает...
если не соответствует, то переходит к следующему case
если приэтом в ходе выполнения доходит до break, то происходит выход из блока switch
1) параметр условия case - могут быть только константы?
2) параметр switch передается переменная и она (условие выборки) в ходе выполнения блока может меняться?
Сравнивает, если находит нужный кейс, то он и все кейсы после него выполняются. Поэтому типично ставят брейки.
2) параметр switch передается переменная и она (условие выборки) в ходе выполнения блока может меняться?
1) да, только константы
2) нет, не может, используется то значение, которое было на момент вызова оператора
в следующем кейсе уже ничего не сравнивается, все последующие кейсы выполняются, пока не встретится break
switch (c){
Рецидивист, однако :(((