Ах, эти страшные, сложные, милые, простые классы.

Тема в разделе "Флудилка", создана пользователем DetSimen, 16 янв 2018.

  1. railmisaka

    railmisaka Гик

    Вы определенно правы.
    Я руководствовался принципом "не все сразу".
     
  2. DetSimen

    DetSimen Guest

    ему и так не надо. строка меняется постоянно. нужен просто метод Out или Run(string) для вывода.
     
  3. DetSimen

    DetSimen Guest

    я пашол синячить, ручка вава, да и пора уже. до понедельника.
    создано с помащю TTimerList & MessageList

     
    Последнее редактирование модератором: 27 янв 2018
    CYITEP_BAC9I нравится это.
  4. DetSimen

    DetSimen Guest

    Ну вот, теперь, когда ручка зажила (а не ходите, дети, грущиками работать, учитесь хорошо), морозяки кончились и зарядила снежная круговерть, пора наливать мне дешевого рома, в тоске о жарких странах, золотому песку и крутобёдрых мулатках, да продолжать наше повествование дальше...

    Сейчас наш класс состоит из единственной функции, которая читает сенсор и нескольких полей, задающих признаки и состояние этого сенсора. Но, спросите вы, разве мы не можем просто написать эту функцию, без всяких классов, которая просто читает сенсор и отдает его значение не чаще определенного интервала времени? Зачем целый класс городить? Конечно же можем, щас мы даже так и сделаем. Обьявим, для начала, глобально, несколько переменных.

    Код (C++):
      byte         SensorPin;         // номер пина, на котором "сидит" наш сенсор
      unsigned long SensorLastTime;        // время последнего к нему обращения
      int         SensorLastValue     // последнее считанное с сенсора значение
      unsigned long SensorReadInterval;        // интервал, чаще которого, его  не нужно читать
     
    да и напишем, сопсно, функцию

    Код (C++):
    int ReadSensor(void)
    {

      unsigned long now = millis();  // прочитаем время на сейчас

    // Если с момента последнего обращения прошло меньше SensorReadInterval секунд, просто отдадим
    // то, что прочитали последний раз, не дергая пин лишний раз

      if (now - SensorLastTime < SensorReadInterval) return SensorLastValue;

    // Ну а уж если времени прошло больше, куда деваца, надо читать

      int result = 0;

      for (byte i=0; i<16; i++) result += analogRead(SensorPin);

      SensorLastTime     = now;        // запомним время, когда мы последний раз читали
      SensorLastValue      = result >> 4;    // и запомним, что прочитали при этом

      return SensorLastValue;        // ну и то, что прочитали, то и отдадим.
    }
     
    такой подход тоже имеет право на жизнь, но вот не нравятся мне чота 4 переменные на каждый сенсор, болтающиеся в глобальных данных. Запортить их может любая другая функция этого (да и не только этого) модуля и смотри потом до дыр в глазах, кто и в какой момент это сделал. По-хорошему надо бы эти глобальные переменные спрятать. А куда их можно спрятать? Тут на ум сразу приходят статические переменные функций. Переменные, обьявленные в функциях как static, не теряют (хранят) свои значения между вызовами функции. Непонятно? Ну вот представим, что нам нужно каждый день ходить в волшебный лес и кормить там тигра. Лес - это у нас функция, а тигр - переменная (пока обычная) в этой функции. Если мы напишем так:

    Код (C++):
    void Forest(void)
    {
        int Tiger;
        Serial.println(Tiger);
    }
    то в сериал выведется любое случайное значение, которое осталось валяться на стеке. То есть при входе в лес, тигр может находиться где угодно, местоположение его не определено, и нам придется бродить полдня по лесу с тяжелым рюкзаком корма, искать его и плакать. Мало того, при выходе из леса тигр вапще исчезает (ну лес то волшебный), и появляется в случайном месте только когда мы входим в лес. Поэтому добрые люди сразу же, при обьявлении переменной присваивают ей значение.

    int Tiger = 0;

    ну слава Богу, теперь, как только мы входим в лес, тигр уже ждет нас возле дерева № 0, по крайней мере бегать по всему лесу уже не надо. Но вот беда, дерево №0 находится в таком заросшем овраге, что можно все штаны порвать, пока до него доберешься. Поэтому мы берем тигра, и как чумадан, относим его в более удобное место, например к дереву №10 (Tiger = 10). Со спокойной душой кормим тигра и уходим до завтра. А завтра что? Правильно, тигр (глупое жывотное, прям как мой кот) ждет нас опять возле дерева №0, скатина такая. Опять надо продираться через чертополох, оставляя клочки штанов на ветках. Плахая киса, не запомнил, куда мы его в прошлый раз притащили. И в следующий раз мы уже входим в лес вооруженные толстой, брутальной веревкой, к которой блестящими заклепками присобачена ржавая бирка, на которой нацарапано волшебное слово static. В очередной раз обдирая штаны до трусов, извлекаем тигра из чертополоха и веревкой привязываем упирающегося тигра к более удобному нам дереву №10.

    static int Tiger = 10;

    всё, галупчег, мы победили. Теперь при входе в лес мы точно знаем что

    1. Там есть тигр, которого скатина леший нам навязал кормить, пока он в отпуске.
    2. Тигр конкретно ждет нас у дерева №10.

    И мало того, если вдруг нам покажется, что возле дерева №10 слишком много комаров, мы легко можем привязать тигра к любому другому дереву (присвоить в этой функции переменной, обьявленной как static, любое другое значение), зная, что при следующем входе в лес он будет именно там, куда мы его привязали. Например, написав где-то дальше по тексту Tiger = 20; мы точно знаем, что завтра (при следующем входе в функцию) тигр будет ждать еду конкретно возле дерева №20. А можно при входе написать Tiger++ и перебрать все деревья в лесу, чтоб ему не скучно было в одном и том же месте сидеть. Применим, написанное, попутно немношко подсократив, ведь номер пина мы можем передавать в функцию как параметр.

    Код (C++):
    int ReadSensor(byte pin)
    {

      static unsigned long     lasttime = 0;          // время последнего чтения
      static int              lastvalue = -1;        // сигнал, что pin еще не читали  
      static int          readinterval=2000;    // интервал между чтениями 2000 мс (2 секунды)  


      unsigned long now = millis();  // прочитаем время на сейчас

    // если датчик уже читали (lastvalue>=0) И время последнего чтения меньше чем readinterval
    // то просто отдаём последнее прочитанное значение из lastvalue

      if ((lastvalue>=0) && (now-lasttime<readInterval)) return lastvalue;

    // Ну а уж если времени прошло больше, или датчик еще совсем не читали (lastvalue<0), то куда деваца, надо читать

      int result = 0;

      for (byte i=0; i<16; i++) result += analogRead(pin);

      lasttime     = now;        // запомним время, когда мы последний раз читали
      lastvalue      = result >> 4;    // и запомним, что прочитали при этом

      return lastvalue;        // ну и то, что прочитали, то и отдадим.
    }
    И ведь надо же, и так будет работать правильно. Можно, кстати, и интервал чтения передавать как параметр, тогда еще от одной переменной можно избавиться, от readinterval. Примерно так

    Код (C++):
    #define PHOTOPIN  A0             // пин, на котором сидит фоторезистор
    #define PHOTOREADINTERVAL 2000UL // интервал чтения фоторезистора - 2 секунды


    int ReadSensor(byte pin, unsigned long interval)
    {

      static unsigned long     lasttime = 0;          // время последнего чтения
      static int              lastvalue = -1;        // сигнал, что pin еще не читали  


      unsigned long now = millis();  // прочитаем время на сейчас

    // если датчик уже читали (lastvalue>=0) И время последнего чтения меньше чем readinterval
    // то просто отдаём последнее прочитанное значение из lastvalue

      if ((lastvalue>=0) && (now-lasttime<interval)) return lastvalue;

    // Ну а уж если времени прошло больше, или датчик еще не читали, то куда деваца, надо читать

      int result = 0;

      for (byte i=0; i<16; i++) result += analogRead(pin);

      lasttime     = now;        // запомним время, когда мы последний раз читали
      lastvalue      = result >> 4;    // и запомним, что прочитали при этом

      return lastvalue;        // ну и то, что прочитали, то и отдадим.
    }

    void loop(void)
    {
      ReadSensor(PHOTOPIN,PHOTOREADINTERVAL);
    }
    с какой бы частотой не вызывался loop(), наш фотосенсор не изотрется до дыр от частого чтения. Можно на этом и остановиться, наверное? Ога, ровно до того момента, как нам захочется подключить второй аналоговый сенсор, например MQ2. А чо в этом такого, спросите вы, ведь функция для чтения у нас уже есть, знай передавай туда номер пина да желаемый интервал между чтениями и получай себе данные. Как бы не так. Смотрим.

    Код (C++):

    #define PHOTOPIN  A0             // пин, на котором сидит фоторезистор
    #define PHOTOREADINTERVAL 2000UL // интервал чтения фоторезистора - 2 секунды

    #define MQ2PIN  A0                 // пин, на котором сидит MQ2
    #define MQ2READINTERVAL 5000UL   // интервал чтения датчика дыма MQ2 - 5 секунд
     
    И если счас просто взять и написать:
    Код (C++):

    void loop(void)
    {
      int photovalue = ReadSensor(PHOTOPIN,PHOTOREADINTERVAL);  // читаем фоторезистор
      int mq2value = ReadSensor(MQ2PIN, MQ2READINTERVAL);
    }
    то давайте представим, а чему будет равно значение mq2value? Ну хотя бы примерно? Дак вот вовсе не тому, что мы прочитали с датчика MQ2, а то что мы прочитали с фотодатчика. Непонятно. А почему? А потому что в первый раз, когда мы обратились к ReadSensor, она сохранила в своих static переменных время последнего обращения к фотодатчику и последнее прочитанное из него значение. А когда мы в следующий раз зашли в эту же функцию, но уже с номером пина MQ2 и интервалом его чтения. то вот это:

    Код (C++):
      if ((lastvalue>=0) && (now-lasttime<interval)) return lastvalue;
    сказало нам, что мы читали датчик несколько микросекунд назад и неважно какой это был датчик, поэтому функция нам и вернула то, что она в последний раз прочитала с фотодатчика. Вот блин засада. Опять чешем репу. Канеш, тут сразу видится, по крайней мере, 2 простых решения, либо загромождать область глобальных данных четырьмя переменными для каждого датчика и вызывать 1 функцию, передавая ей их в качестве параметров, либо писать столько функций, сколько у нас в программе будет даччиков, скрывая ихие данные в статические переменные этих функций.
     
    ИгорьК нравится это.
  5. DetSimen

    DetSimen Guest

    Первый путь - возможен, но некрасив, в конце концов область глобальных переменных вскоре превратится в "Войну и Мир", в которой мы и сами ничего не поймем, где что лежит и к чему относится. А так как переменные осмысленно мы называть не любим, а тщательное написание комментариев нам как серпом по тестикулам, то стабильная работа кода нам может только сница. А особенно если кто-то любит обьявлять глобальные массивы и не проверять выход за границы при обращении к нему, то всё, вэлкам на форум с воплями "ПАМАГИТИ!!! Разберитесь в моём гагнакоде. Яжнепрограммист!!!".

    Второй путь приводит к бессмысленному раздуванию кода программы, аптамуш, для каждого датчика компилятор генерирует свою функцию. Она ничем не отличается от функции для другого датчика, кроме как набором исходных данных. Вот тебе и выбор, б-ть! Либо делать свои данные уязвимыми для всех ветров и массивов, либо писать программу, которая из-за избыточного кода даже в Мегу не влезеть, если даччиков станет хотя бы 5. Пойду рома налью от безысходности... Ан нет, пока подожду, я же тут за классы начал говорить, вдруг оне помогут? Так вот. Классы и были придуманы умными людями (а не все буржуины в психушках стандарты С++ выдумывают) как раз именно для такой концепции, во-первых скрыть свои лишние данные от посторонних, тем самым обезопасив их, а во-вторых не раздувать код одинаковыми функциями. И если мы напишем в своей программе

    TSensor Photo(A0);
    TSensor MQ2(A1);

    во первых, все внутренние данные РАЗНЫХ сенсоров будут РАЗНЫМИ и недоступными ни одному злобному Буратине снаружи. А во-вторых, функция ReadSensor в коде программы будет только одна. Не одна для каждого датчика, а именно одна ДЛЯ ВСЕХ датчиков одного класса TSensor. Захотите датчик влажности подключить - да пожалуйста, обьявляйте еще одну переменную класса TSensor, настраивайте желаемый пин и интервал чтения и вперед, читайте и его тоже ЭТОЙ ЖЕ САМОЙ функцией, код самого класса не вырастет ни на байт. Вы хотите спросить, а как одна функция знает, для какого даччика её вызвали? Дак очень просто. Дело в том, что в функцию класса в качестве неявного параметра (мы про это не знаем и не видим, но это есть) передается указатель на конкретный объект, который ее вызвал. примерно так:

    мы пишем

    int Value = Photo.ReadSensor();

    а компилятор неявно преобразует это в

    int Value = Photo.ReadSensor(&Photo);

    то есть, неявно передает функции адрес обьекта, который ее вызвал. А так как адреса у всех обьектов разные, то одна единственная функция может работать с размыми наборами данных для каждого датчика. Налицо сокращение кода, особенно, когда однотипных даччиков Over чем 9000.

    Пока делаем выводы, я пойду себе налью себе рома, штоли. Ручка сётаки вава. Не ходите дети в грущики.

    А в след. раз будем разбирать брррр... указатели на функции.
     
    ИгорьК нравится это.