Наследование классов

sturmber
Offline
Зарегистрирован: 27.08.2020

Подскажите как лучше реализовать и написать пример кода.

Хочу сделать класс, например Work (родительский класс), от которого будут наследоваться Teacher и Driver

у всех трех будут функции start() и end() выполняющие разный код.

создаю переменный Teacher t; Driver d; и указатель Work *w;

указателю присваиваю ссылку (в зависимости от условий) на t или d

и выполняю функции start() и end() 

 

без использования классов реализовал это  указателями на функцию,

но хочется сделать код красивее.

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

обяви в родительском классе абстрактные виртуальные функции start() и end(), а реализацию их напиши в override дочерних классах.

Вопщем, много читай. 

Logik
Offline
Зарегистрирован: 05.08.2014

делай так

class Work
{
  void prn(char* c){Serial.println(c);}
public:
  virtual void start(void)=0; 
  virtual void end(void)=0; 

protected:  
  virtual void print(char* c){prn(c);}
};

class Teacher : public Work
{
  void start(void){print("Teacher start");} 
  void end(void){print("Teacher end");}  
  
};

class Driver : public Work
{
  void start(void){print("Driver start");} 
  void end(void){print("Driver end");}  
  void print(char* c){Work::print(">>>>>");Work::print(c);Work::print("*****");}  
};


void setup() {
  // put your setup code here, to run once:
 Work* w;
 Teacher T;
 Driver D;
 
 Serial.begin(9600);

 w=&T;
 w->start();

 w=&D;
 w->start();

}

void loop() {
  // put your main code here, to run repeatedly:

}

Я немного отсебятины при печати добавил. А то пример совсем скучный будет.

выводит в порт

Teacher start
>>>>>
Driver start
*****
 

 

 

sturmber
Offline
Зарегистрирован: 27.08.2020

Спасибо!

а если вызвать функцию, которая объявлена как виртуальная в родительском, но нет в дочернем -

вызовет критическую ошибку при исполнении или пойдет на пустой цикл?

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

sturmber пишет:

а если вызвать функцию, которая объявлена как виртуальная в родительском, но нет в дочернем -

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

b707
Онлайн
Зарегистрирован: 26.05.2017

DetSimen пишет:

обяви в родительском классе абстрактные виртуальные функции start() и end()

не обязательно абстрактные, главное чтоб виртуальные.

Logik
Offline
Зарегистрирован: 05.08.2014

sturmber пишет:

Спасибо!

а если вызвать функцию, которая объявлена как виртуальная в родительском, но нет в дочернем -

вызовет критическую ошибку при исполнении или пойдет на пустой цикл?

Не думай о виртуальных функциях как о чем то не знакомом тебе! Это все те же указатели на функцию, только красиво, как просил. И св-ва их такие же. Каждый потомок  переопределяет их - всего лишь присваивает свой указатель на свою функцию. Если не переопределял - останется значение от последнего в иерархии присвоившего. Если указатель объявили но он 0 - абстрактный класс, вызов даст ошибку. Так что думай о виртуальных как о указателях на функцию и вопросов не будет. Но с виртуальными нельзя делать все что можно с указателям. Указатели на функцию куда более мощные, а значить и опасные. Потому их и "притупили" и в ООП обернули, чтоб недоумки реже ошибались. 

b707
Онлайн
Зарегистрирован: 26.05.2017

тоже вопрос по наследованию, раз уж ТС начал

Допустим, я хочу создать свой класс Child, производный от какого-то стороннего класса Base., при этом хочу переопределить в новом классе метод print(). который, к сожалению, в базовом классе не описан как виртуальный.

 

#include <stdio.h>
class Base {
 public:
     void func1() {print();};
     void print() {printf("base class \n");};
};

class Child: public Base {

public:
     void func3() {print();};
     virtual void print() {printf("child class \n");};
};


int main() {
Child pi;
printf("func3 use print()from ");
pi.func3();
printf("func1 use print()from ");
pi.func1();
}

В итоге имеем ситуацию, что когда метод print() вызывается методами класса Child - вызывается новый метод, а когда вызов происходит из методов класса Base - то и print() вызывается тоже "старый"

как иллюстрация - вышеприведенный код печатает:

func3 use print() from child class 
func1 use print() from base class 

Вопрос - можно ли решить проблему ( чтобы всегда вызывался метод Child::print()) без изменения класса Base?

 

b707
Онлайн
Зарегистрирован: 26.05.2017

что пробовал

 попытка переопределить базовый метод при помощи using как приватный:

class Child: public Base {
private:
   using Base::print;

public:
     void func3() {print();};
     virtual void print() {printf("child class \n");};

};

ни на что не влияет.

Попытка вовсе вычистить его с помощью delete не компилируется:

class Child: public Base {

public:
     void func3() {print();};
     virtual void print() {printf("child class \n");};

     void Base::print() = delete;
};

 

rkit
Онлайн
Зарегистрирован: 23.11.2016

Нельзя.

Logik
Offline
Зарегистрирован: 05.08.2014

нет

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

b707 пишет:

Допустим, я хочу создать свой класс Child, производный от какого-то стороннего класса Base., при этом хочу переопределить в новом классе метод print(). который, к сожалению, в базовом классе не описан как виртуальный.

В итоге имеем ситуацию, что когда метод print() вызывается методами класса Child - вызывается новый метод, а когда вызов происходит из методов класса Base - то и print() вызывается тоже "старый"

Вопрос - можно ли решить проблему ( чтобы всегда вызывался метод Child::print()) без изменения класса Base?

Так в чем проблема-то?

Разработчик базового класса решает, какой метод будет вызываться в случае наследования. Есть у него такое право или нет?

Как говорится: "это не бага, это - фича".

Logik
Offline
Зарегистрирован: 05.08.2014

//Как говорится: "это не бага, это - фича".

Даже хуже. Это архитектура. Некоторые разработчики, особо сволочные ;) , еще и имеют наглость писать private. И это в исходниках на моем то компе! Но мы же не дети. Раз исходник найден - то private там или нет уже не важно ))) 

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

b707 пишет:

Вопрос - можно ли решить проблему ( чтобы всегда вызывался метод Child::print()) без изменения класса Base?

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

b707
Онлайн
Зарегистрирован: 26.05.2017

Спасибо мужики

Logik
Offline
Зарегистрирован: 05.08.2014

эээээ.....

а поговорить?

b707
Онлайн
Зарегистрирован: 26.05.2017

Logik пишет:

а поговорить?

да о чем тут говорить? :) - я и сам знаю, как это делать правильно - добавить в базовый класс спецификатор virtual для метода print()

Но в моем случае базовый класс - это чужая библиотека, и хотелось бы обойтись без ее модификации. Надеялся - а вдруг есть какой хитрый способ? :)

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Тута, кроме virtual есть еще одна хитрость.  Если у тебя в родительском классе есть виртуальная функция, например 

virtual void TSensor::Error(int);

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

void TAnalogSensor::Error(int8_t); 

то компилятор это съест, но это будут две разные функции(из-за различающегося типа параметра)  и они нихрена полиморфировать друг в друга не будут.  Чтоб убедиться, что ты перекрываешь родительскую функцию, надо писать так:

void TAnalogSensor::Error(int); override

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

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

b707 пишет:

 

Но в моем случае базовый класс - это чужая библиотека

модификация сводится к одному слову virtual, и перемещению этого обьявления в секцию protected (если она была в private).  :) 

b707
Онлайн
Зарегистрирован: 26.05.2017

DetSimen пишет:

модификация сводится к одному слову virtual, и перемещению этого обьявления в секцию protected (если она была в private).  :) 

любая модификация - да хоть на запятую - приводит к необходимости распространять кастомную версию библиотеки со своим кодом. И как об этом не пиши в описании - обязательно найдется юзер, который поставит стандартную либу с гитхаба и потом будет орать, что ничегго не работает :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

b707 пишет:

любая модификация - да хоть на запятую - приводит к необходимости распространять кастомную версию библиотеки со своим кодом. И как об этом не пиши в описании - обязательно найдется юзер, который поставит стандартную либу с гитхаба и потом будет орать, что ничегго не работает :)

Это как раз совсем не проблема. Вернее, проблема, конечно, но она не имеет отношения к модификации библиотеки от слова совсем.

В Вашей фразе слишком много слов. Если бы Вы написали просто "обязательно найдется юзер" и ничего больше - Вы бы обозначили истинную проблему, а не надуманную.

А истинная проблема в том. что что бы и как бы Вы ни делали (или "не делали"), как бы не извращались и чтобы не писали в описании, обязательно найдётся юзер!!! Поэтому, делайте спокойно как Вам надо и не парьтесь насчёт юзера - он всё равно найдётся по любому, и от Вас (Ваших действий) это зависит чуть менее, чем никак.

sturmber
Offline
Зарегистрирован: 27.08.2020

натолкнулся на интересный "глюк" компилятора (Visual Studio Code), чуть голову не сломал (ответ будет ниже, для тех кто захочет сам понять проблему)
код компилируется без ошибки:

Scripts about;
ScriptCapturePoint capturePoint;
Scriptsarray[] = {&about, &capturePoint, &capturePoint};
byte array_size = sizeof(array)/sizeof(Scripts*) - 1;
byte cur_menu = 0;
......................
      switch (c){
        case 0: ;      
        case '*'
               if(cur_menu > 0array[--cur_menu]->menu();
        break;
        case '#'
               if(cur_menu < array_sizearray[++cur_menu]->menu();
        break;
        case 'D'
               array[cur_menu]->start();
        break
        };

при нажатии '#' происходило переключение на следующий пункт меню (cur_menu становилось равно 1), но тут же сбрасывалось в начало (0)

 

 

 

 

Решение:

#################################################

В завершении не стоит break

case 0: ;

 

Почему переменная сбрасывалась???

или вся программа уходила в начало и переменные инициализировались??

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sturmber, скорее всего это, как часто бывает, не глюк компилятора, а глюк программиста. Но понять что происходит по куску кода невозможно. Если бы Вы привели полный пример (как можно более короткий, но чтобы проблема оставалась), можно было бы посмотреть и подумать.

Так-то на первый взгляд, всё логично. Если нажата "#", то cur_menu становится 1, а если потом не нажато ничего,  то в строке №ХЗ выполняется --cur_menu и она снова становится нулём. А Вы чего-то другого ожидали?

(Кстати, о "строке №ХЗ" - ЗАДОЛБАЛИ!!! Вставляйте же, наконец код правильно!!!)

sturmber
Offline
Зарегистрирован: 27.08.2020

неожиданно, что компилятор пропустил без ошибки

думал, это обязательная структура - завершить case .. break

      switch (c){
        case 0: ;      
        case '*'
        break;
        };
 
DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Читать надо больше

b707
Онлайн
Зарегистрирован: 26.05.2017

DetSimen пишет:

Читать надо больше

Именно!

sturmber - отсутствие break в case - это не баг, а фича, используемая для обьединения веток switch.

 

sturmber
Offline
Зарегистрирован: 27.08.2020

я правильно понимаю логику оператора switch..case:
программа сравнивает с первым case, если соответствует, то исполняет его и если доходит до ещё одного case, то опять сравнивает...
если не соответствует, то переходит к следующему case

если приэтом в ходе выполнения доходит до break, то происходит выход из блока switch

1) параметр условия case - могут быть только константы?
2) параметр switch передается переменная и она (условие выборки) в ходе выполнения блока может меняться?

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Сравнивает, если находит нужный кейс, то он и все кейсы после него выполняются. Поэтому типично ставят брейки. 

v258
Offline
Зарегистрирован: 25.05.2020

sturmber пишет:
1) параметр условия case - могут быть только константы?
2) параметр switch передается переменная и она (условие выборки) в ходе выполнения блока может меняться?

1) да, только константы

2) нет, не может, используется то значение, которое было на момент вызова оператора

b707
Онлайн
Зарегистрирован: 26.05.2017

sturmber пишет:
я правильно понимаю логику оператора switch..case: программа сравнивает с первым case, если соответствует, то исполняет его и если доходит до ещё одного case, то опять сравнивает...

в следующем кейсе уже ничего не сравнивается, все последующие кейсы выполняются, пока не встретится break

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sturmber пишет:

      switch (c){

        case 0: ;      
        case '*'
        break;
        };

Рецидивист, однако :(((