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

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017
Эпиграф:   Не бойтесь писать классами... (ударение каждый поставит где нраица)
 
Это типа эссе для новичков, зеленых новичков и новичков вапще с ног до головы облитых зелёнкой. Главное в новичке - любознательность и желание задумываться и учиться, а не уровень громкость вопля "ПАМАГИТИ!!!!" на форуме.  
Сразу скажу, я вообще ни разу не профессиональный С++ программист, по образованию я - физик, по профессии - олкаголег, по хобби - дачник, по призванию и по жизни- лентяй. Последнее я умею делать профессионально (сам удивляюсь, как до полтинника дожил и жена не убила).
 
Это просто попытка такого же, как и вы, полудилетанта, рассказать простыми словами, что не надо бояться классов, не так страшен квадрат, как его Малевич. (на самом деле надо бояться классов, но только таких, как у quonе). Не претендуя на глубину изложения классовой модели С++, я постараюсь рассказать только то, что может быть полезно для простых и не очень поделок начинающего Дуниста. 
 
Класс это очень удобная, самодостаточная, законченная модель программного (и не только) объекта. Аналог в реальной жизни - да любая вещь, сделанная человеком: автомобиль, настольная лампа, часы, телевизор, да даже родной/любимый собутыльник стол-книшка, и тот является объектом. У каждой вещи есть функция, которую она исполняет, для чего её сопсно сделали, а потом и купили, и есть какой-то, где-то более простой, как в часах, где-то сложный, как в автомобиле, интерфейс взаимодействия с человеком. Каждый класс, как и вещь, должен быть самодостаточным, законченным изделием. Думаю, было бы не очень удобно, допустим, для того, чтобы подкорректировать время в настенных часах, идти, выдирать блок кнопок из стиралки, чтобы подключить их к часам для настройки. А для того, чтобы стиралка знала, сколько времени стирать, разбирать часы и вставлять оттуда в нее блок подсчета времени. А потом - обратно. Так что первое правило класса - хранить в себе все данные своего обьекта, не завися ни от чего снаружи. 
 
Второе правило класса (и вещи) - скрытность. Все то, что не желательно давать настраивать шаловливым ручкам пользователей - должно быть скрыто. Согласитесь, немногие из нас знают, что в телевизоре есть приемный тракт, усилительный тракт, блок разверток и всякие остальные лампы подсветки,  нам это не интересно, у нас есть пульт и мы хотим нажать на нем кнопку и смотреть любимых телепузиков под пиво, лёжа на диване, не задумываясь, как там идет сигнал, откуда он берется и как выводится на экран. Всё это от нас скрыто.  Если б мне каждый раз перед просмотром телека приходилось по полчаса вручную настраивать частоту строк, частоту кадров, размер экрана и (не дай Бог) подстраивать трепецеидальные искажения, я б на третий раз просто расколотил бы его нахрен без всякого сожаления. Ну, может быть всплакнул бы потом и, скорее всего, напился. И всё. Наружу у телека должно торчать только несколько кнопок и ИК датчик, через которые я буду им командовать. Так и в классе - пользователю должны быть доступны ровно те данные, которые требуются для правильной работы самого класса, для исполнения им своего предназначения, ну еще может быть чучуть для тонкой подстройки. 
 
Теперь рассмотрим что такое свойства и что такое методы. Возьмем, например, тот же любимый стол-книшку, за которым так удобно сидеть на веранде, когда придут гости и вести задушевные разговоры после бани под ледяную водочку.  У стола есть физические свойства, длина, высота, ширина и т.д, некоторые из которых зависят от того, разложен стол или нет. У класса такие свойства называются поля и, обычно, называются именами существительными, отвечающим на вопрос "Что?" - "длина", "номер пина", "время" и т.д. 
Так же у класса должны быть "методы" для работы с полями. Например, приходят гости, мы выкатываем из дальнего угла запыленный стол, применяем к нему методы "Протереть", "Разложить" и "Накрыть" и хоба!, через некоторое время уже можно за ним спать, уютненько устроившись буйной головушкой в мягенькой, вкуснющей селедке под шубой. Стол свою функцию выполнил. Можно применять к нему методы "Сложить" и "Убрать". Заметим, что в методах "Сложить" и "Разложить" внутри автоматически  меняется свойство (поле) "Длина". То бишь, методы - это глаголы, означающие действия ("Что делать?/что сделать?") над полями класса, открытыми, так и спрятанными глубоко унутре (а там еще и неонка).
 
Но хватит демагогии, допустим, у нас есть какой-нибудь аналоговый сенсор, это может быть даччик влажности в цветочном горшке, или простой фоторезистор, а может быть вершина китайского сумасшедшего гения - датчик серии MQ-2 - 135. Суть аналогового датчика проста и понятна, измерять на определенном аналоговом пине напряжение и отдавать его программе, поэтому плодить каждый раз классы отдельно для фоторезистора, отдельно для MQ-2 или датчика влажности, смысла, я думаю, никакого нет, можно просто написать класс "Аналоговый сенсор" и использовать его спокойно потом со всеми шилдами или самопальными платами на своё усмотрение. Главное, работа с этим типом сенсоров будет единообразной и упорядоченной. Дак давайте этим и займемся. 
 
Чтобы создать свой класс перво-наперво надо создать 2 файла, заголовочный .h файл и файл  реализации .срр (мы ж крутые, будем писать классы на лживом С++ по взрослому). .h файл в С++ рассматривается как своего рода "Протокол о намерениях" и содержит только ОБЪЯВЛЕНИЕ класса, то бишь описание всех его полей и методов скрытых и открытых, каэ это мы видим и которые хотим использовать в своем классе. Еще там иногда объявляются всякие вспомогательные типы, которые в этом классе могут понадобиться и которые будут видны снаружи, мы и до них дойдём, но потом.  Директива #include "имяфайла.h" несколько туповата, и просто вставляет весь текст файла целиком в то место где она написана, поэтому, если h файл используется в разных частях проекта, то он будет вставлен в итоговый текст программы ровно столько раз, сколько встретился в директиве #include, что приведет к ошибке множественных деклараций. Чтоб этого не случилось, настоящие C программеры пишут в каждом h файле примерно следующее
 
#ifndef имяфайла_H  // если не определен символ имяфайла_H
#define имяфайла_H  // перво наперво определить его
 
   потом здесь пишут сопсно все свои обьявления и заканчивают файл так 
 
#endif // конец ifndef
 
Это позволяет включить h файл в результирующий текст программы всего один раз, когда встретилась директива #include. Вторая и последующая директивы #include не возымеют никакого действия, ибо символ "имяфайла_H" уже определен и все последующие строчки до #endif тупо пропускаются.  Как по мне, это очень громоздкая конструкция, я ее не люблю и не использую, и выкидываю из всех библиотек, которые иногда приходится править, а чтобы h файл несколько раз не включался в текст программы добрые люди придумали директиву 
 
#pragma once
 
которая делает всё тоже самое, что и предыдущая громоздкая конструкция, только не в пример легче запоминается. Используем её. 
 
Ну вот, настало время определиться с именем нашего класса и с именами файлов. Я, еще со времен Турбо-Паскаля, привык именовать все свои классы начиная с буквы T (type), а указатели на них - начиная с буквы P (pointer), так сложнее запутаться, ну для меня конечно. Я вас, естесственно, ни к чему не принуждаю, каждый волен сам выбирать систему имен для своих программ, но здесь я буду делать как привык. Итак, мы делаем класс для аналогового даччика, поэтому логично обозвать его TSensor, что мы и сделаем. Соответственно, заголовочный файл у нас будет TSensor.h а файл реализации TSensor.cpp. 
 
Создадим их в любом текстовом редакторе, хоть в Arduino IDE, потом откроем заголовочный h файл и прям первыми строками напишем:
 
#pragma once
#include "Arduino.h"
 
потом поместим гордо строчку 
 
class TSensor 
{
}
 
и глубоко задумаемся. На самом деле нас уже есть с чем поздравить, мы только что обьявили реально работающий, синтаксически верный, хоть и пустой класс. Но в этом виде он абсолютно бессмысленен и умный (не чета мне) компилятор скорее всего даже выкинет его из результирующего текста программы. Чтоб класс стал полезен, пришло время подумать, что же мы хотим видеть у нашего сенсора, какие у него должны быть поля (свойства)  и  какие методы (действия над ними). Первое, что характеризует сенсор - номер пина, куда он подключен. Номер пина достаточно задать 1 раз при создании класса и не давать пользователю его изменять во время выполнения программы, ибо я считаю, что никто, обладающий хотя бы спинным мозгом, не полезет на-лету перетыкать сенсор в другой пин на включенной Ардуине. Да и ардуина скорее всего на такое абидица и перестанет отвечать. Значить, по правилам хорошего класса, номер пина должен быть скрытым полем. В С++ скрытые поля создаются директивами private и protected. Я буду использовать protected, пока без объяснений, потом, когда вы более плотно начнете разбираться с классами, вы поймете чем они отличаются, но на сейчас, нам достаточно знать, что protected менее строго ограничивает доступ к полям и методам, а private - более строго, но нам сойдёт и protected. 
Интересующимся: гугаль пока в Роисси не заблокировали. 
 
Итак:
 
class TSensor 
{
 protected:
  
  byte fpin;   // поле размером байт "номер пина", скрытый от пользователя
}
 
так как пинов в ардуине существенно меньше 255, то все их номера прекрасно помещаются в переменную типа байт (еще и место останется). Тем более, байт есть существо беззлобное и беззнаковое, а отрицательных пинов в AVR я отродясь не видел,  поэтому byte (ну или uint8_t в терминах GCC С++) нам для хранения номера пина подойдет более чем полностью. Што? Почему называется fpin? Ну так я же родом из Турбо Паскаля, я честно предупреждал, а там защищенные поля принято начинать с F, а параметры, передаваемые в функцию - с А. Let it be, я так привык.
 
Теперь во весь рост встает вопрос, ежели мы создали скрытое поле, которое снаружи класса не видно, как же запхать-то в него этот пресловутый номер пина, к которому без всякой уже надежды подключен наш грусно висящий пока "на полшестого" даччик?  И тут со сверкающей вершины стандарта С++ нам на помощь летят конструкторы. Нет. Не те, которые в КБ на ватманах всяких очередной, приговоренный в Туве разбица, спутник чертют, те в такой ситуации способны только расшибиться в мясо. Конструктор - это специальная функция, которая вызывается единственный раз, именно при, и, самое главное, ДЛЯ создания класса и предназначена как раз для задания начального состояния класса, типа значения полей и таблиц. На самом деле, хотя мы в нашем классе еще не написали конструктор, но компилятор С++ неявно и заботливо вставил их в описание класса целых два, пустой и копирующий. Не надо протирать глаза водкой, в нашем тексте мы их не увидим, но они там есть. Только скрытые даже от нас. И даже если мы не напишем свой конструктор, то при создании класса по умолчанию вызовется пустой констуктор, в задачу которого входит присвоить начальные нули всем полям класса и инициализировать таблицу виртуальных функций (и не спрашивайте ачоэтотакое?, это для взрослых). Тоись, если мы в своей программе объявим переменную
 
TSensor PhotoSensor;
 
то ошибки не будет, компилятор вставит здесь для инициализации вызов пустого конструктора, который тупо заполнит нулями значения всех простых полей (которое у нас аш целое одно), и вызовет конструкторы по умолчанию для полей-классов, если они не обьявлены указателями (да, да поля могут быть не только int, byte, float или long, но и классами и структурами и массивами, да любыми сложными типами, но это тоже оставим пока для взрослых) соответственно наш fpin тоже будет равен нулю по умолчанию, всегда и постоянно, ведь снаружи мы его изменить не можем, он защищен от подглядывания директивой protected. Но это же ваще никуда не годитсяа! Нам же надо чтобы pin был вполне конкретно-определенный. Ну штош, придётся писать свой конструктор. На самом деле, конструктор - это простая функция с именем, ровно таким же, как у самого класса и с числом параметров, ограниченным только больной фантазией его создателя. И еще, директива видимости protected, начинаясь от момента обьявления, простирается до тех пор, пока не встретится более другая директива видимости, а конструктор наш должен быть видим снаружи, т.е. должен быть публичным, доступным для широкой общественности, поэтому напишем перед ним новую директиву, public. Вот что примерно получится:
 
class TSensor 
{
 protected:
  
  byte fpin;   // поле "номер пина" типа байт, скрытый от пользователя
 
 public:
  TSensor(byte apin); // конструктор, открытый все ветрам и пользователям
}
 
функция-конструктор никада не отдает никаких значений, поэтому имя типа перед ней ставить не надо.
 
сразу же открываем свой .срр файл (на этот момент он должен быть еще пустой) и пишем
 
//-------------------------------------
// срр файл
 
#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
// чтоб знать, реализацию для чего мы пишем
// срр файл мы не включаем в проект через #include, 
// поэтому #pragma once писать не надо
 
// Реализация нашего класса. Конструктор. В конструкторе мы запоминаем номер пина, который мы ему
// передали в качестве параметра, во внутреннем поле нашего класса.
 
TSensor::TSensor(byte apin)
{
  fpin = apin;
}
 
// капец cpp файла
//--------------------------------------
 
 
из-за префиксов f и a перед pin мне сразу видно, что слева стоит переменная-поле а справа - параметр функции. Но это только вопрос удобства. Два странных двоеточия в TSensor::TSensor это не я два раза по клавиатуре промазал, это специальная конструкция языка, которая показывает, что конструктор TSеnsor относится к классу TSensor и ни к чему более. Префикс TSensor:: ставится перед любой функцией, обьявленной в классе. 
 
Идём дальше. Ничо не забыли? Ну ититькалатить, конечно забыли. У нас же это датчик, он же должен читать данные с pin-a, а на вход мы его настроить забыли. Сделаем это в конструкторе.
 
//-------------------------------------
// срр файл
 
#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
// чтоб знать, реализацию для чего мы пишем
// срр файл мы не включаем в проект через #include, 
// поэтому #pragma once писать не надо
 
// Реализация нашего класса. Конструктор. В конструкторе мы запоминаем номер пина, который мы ему
// передали в качестве параметра, во внутреннем поле нашего класса.
 
TSensor::TSensor(byte apin)
{
  fpin = apin; // запомнили номер pin-а во внутреннем поле класса
  pinMode(fpin,INPUT); // настроили pin как вход
}
 
// капец cpp файла
//--------------------------------------
 
Блин. А как же подтягивающий резистор? Ведь некоторым датчикам, например фоторезистору, подтяжка нужна, а некоторым - нет, а другой класс городить из-за наличия/отсутствия подтяжки совсем не хочется. Вот если бы можно было написать еще один конструктор, в котором передавать классу, кроме номера pin-а еще и необходимость включить подтяжку, было бы здорово.  Ну так такая возможность есть!!!!  У класса может быть сколько угодно конструкторов, отличающихся друг от друга числом и типом параметров им передающихся. По научному это называется перегрузка функций (а конструктор у нас это просто специальная функция класса, но функция же). Объяснять это я не буду, интересующиеся набирают в гугале "перегрузка функций С++" и читают до просветления, нирваны или запоя. Главное, что мы можем создать в нашем классе еще один конструктор, с нужными нам параметрами. Ну и дак сделаем это. 
 
// --------------------------
// .h файл
 
#pragma once
#include "Arduino.h"
 
class TSensor 
{
 protected:
  
  byte fpin;   // поле "номер пина" типа байт, скрытый от пользователя
 
 public:
  TSensor(byte apin);               // конструктор, принимающий только номер пина
  TSensor(byte apin, bool apullup); // а этот еще и включает или нет подтягивающий резистор на pin
}
 
// --------------------------
// капец .h файла
 
как видим, новый конструктор называется точно так-же, только параметров уже 2. Так как подтяжка у нас может быть только включена или выключена, все ее значения прекрасно поместятся в тип bool, который может хранить в себе как раз только два значения, "да" и "нет". Переводя на корявый английский - "true" и "false". А больше нам и не нужно. Да и вообще, памяти у ардуины не так чтобы много, поэтому надо привыкать хранить значения в минимально подходящем для этого типе. Например, совершенно нехрен хранить, как некоторые, номер pin-а в переменной типа int. Во первых она знаковая (найдите мне отрицательный номер пина в Дуне - и я немедленно повешусь на осине, не в силах терпеть этот безумный мир), во-вторых, занимает целых два байта, хотя номер пина прекрасно поместится и в младших 6 битах целого 8-битного байта (в 328 камне). Поэтому, для хранения номера pin-а мы выбрали байт, а для подтяжки - bool. Ээээ, мы же не создали поле, которое будет хранить состояние подтяжки, включена она или выключена.  Глубоко наморщим мозг и подумаем, а оно нам надо, знать в каком состоянии подтяжка?  По-моему - нет, мы должны знать ПЕРЕД созданием класса, нужна она нам или нет, создать класс с нужным значением подтяжки и забыть про нее до конца времен, не будем же мы её дергать туда-сюда.  Поэтому поле для хранения подтяжки нам не нужно, мы его и заводить не будем, сразу пишем: 
 
//-------------------------------------
// срр файл
 
#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
// чтоб знать, реализацию чего мы пишем
// срр файл мы не включаем в проект через #include, 
// поэтому #pragma once писать не надо
 
// Реализация нашего класса. 
 
// Конструктор. В конструкторе мы запоминаем номер пина, который мы ему
// передали в качестве параметра, во внутреннем поле нашего класса и настраиваем его на вход
 
TSensor::TSensor(byte apin)
{
  fpin = apin; // запомнили номер pin-а во внутреннем поле класса
  pinMode(fpin,INPUT); // настроили pin как вход
}
 
 
// Конструктор. В конструкторе мы запоминаем номер пина, который мы ему передали в качестве
// параметра во внутреннем поле нашего класса и настраиваем его на вход с подтяжкой или без
// в зависимости от параметра apullup
 
TSensor::TSensor(byte apin, bool apullup)
{
 fpin = apin;
 if (apullup) pinMode(fpin,INPUT_PULLUP); else pinMode(fpin, INPUT);
}
 
// капец cpp файла
//--------------------------------------
 
 
Странная конструкция if (apullup) , вы не находите?  Ведь ручки прям сами тянутся, особенно у новичков, написать if (apullup==true) ... А я ехидно спрашиваю, а зачем?  У оператора if уши торчат из чистого-пречистого (чесного до последней запятой) С, в начале времен там не было типа bool и выражение в скобках должно было быть типа int и просто проверялось на 0 и не 0. К счастью очередной стандарт ввел тип bool в основные типы языка и гарантировал, что false этта гарантированный 0, а true - гарантированный не 0. Ну а так как в конструктор мы уже передали true или false, то в if дополнительную проверку городить нет никаго здравого смысла. Поэтому смело пишем:
if (apullup) ...
 
Теперь посмотрим на наши конструкторы попристальнее, как на кота, который присел ссать в тапки. Видно, что фактически, код наших конструкторов повторяется на 80%. И там и там мы устанавливаем внутреннее поле fpin и вызываем pinMode, разница только в константе INPUT и INPUT_PULLUP. Это не есть хорошо, потому что памяти в камнях AVR не так уж и много, и раздувать код класса мы позволить себе не можем. Этак вся флэш переполнится, микросхема распухнет, лопнет и нас байтами забрызгает.  Как бы сделать так, чтоб код был один а конструкторов - два (или три, или over 9000)?  К великому щастью, и так сделать можно.  Можно из одного конструктора вызывать другой конструктор.  То есть можно написать один более общий конструктор, который устанавливает большинство полей нужными значениями, а в более специфических конструкторах вызвать сначала общий, а потом уже эти специфические поля заполнить своими специфическими значениями. В нашем случае, более общим конструктором является второй, потому что в первом устанавливается только значение поля fpin и по умолчанию вход настраивается без подтяжки, а во втором делается все тоже самое, только есть еще выбор, устанавливать подтяжку или нет. Значит в первом конструкторе мы можем запросто вызвать второй с параметрами TSensor(apin, false), результат будет идентичен, а кода меньше.  Но как это сделать?  А щас посмотрим. 
 
//-------------------------------------
// срр файл
 
#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
// чтоб знать, реализацию чего мы пишем
// срр файл мы не включаем в проект через #include, 
// поэтому #pragma once писать не надо
 
// Реализация нашего класса. 
 
// Конструктор. В конструкторе мы запоминаем номер пина, который мы ему
// передали в качестве параметра, во внутреннем поле нашего класса и настраиваем его на вход
 
TSensor::TSensor(byte apin): TSensor(apin, false) {}
 
 
// Конструктор. В конструкторе мы запоминаем номер пина, который мы ему передали в качестве
// параметра во внутреннем поле нашего класса и настраиваем его на вход с подтяжкой или без
 
TSensor::TSensor(byte apin, bool apullup)
{
 fpin = apin;
 if (apullup) pinMode(fpin,INPUT_PULLUP); else pinMode(fpin, INPUT);
}
 
// капец cpp файла
//--------------------------------------
 
а просто оттак. После первого конструктора мы вместо открывающей фигурной скобки пишем двоеточие, а после него вызываем второй конструктор как обычную функцию, но с нужными нам параметрами.  Вместо первого параметра мы подставляем apin, который нам передали, а вместо второго, контстанту false. Тело конструктора больше не нужно, поэтому дальше стоят просто фигурные скобки, показывающие, что функция пустая, ее задача просто передать управление другому конструктору. 
А в своей программе мы можем теперь обьявить датчик по-всякому
 
TSensor PhotoSensor(10) // датчик на десятом pin, подтяжка не нужна
TSensor PhotoSensor(10, false) // датчик на десятом pin, подтяжка не нужна
TSensor PhotoSensor(10,true) // датчик на десятом pin, подтяжка нужна
 
есть еще два, неявных конструктора
 
TSensor PhotoSensor; // датчик на нулевом pin, подтяжка ХЗ в каком состоянии
TSensor PhotoSensor(TSensor &&); // неизвестная ё..ная х..ня, в просторечии - копирующий конструктор.
 
 
Но мы же уже выяснили, что первый для нас не представляет никакого интереса, а второй вапще непонятная хрень, и его мы пока точно использовать не будем, по крайней мере пока нам не нужно будет копировать все данные из фотодатчика в датчик влажности. Но это будет уже в прекрасном далёко. . 
 
И заметьте, что первые 2 обьявления одинаковы по результату, потому что, на самом деле неявно для пользователя класса всё равно вызывается конструктор  TSensor(10, false). 
 
 
В продолжении, если местные гуру не забросають меня калом снизу доверху, мы двинемся дальше, будем датчик читать, да попутно millis()-ом крутить да размахивать. 
 
Я понимаю, что написано сумбурно, далеко от идеала, мои сентенции иногда очень далеки от истины и, мягко выражаясь, до неприличия спорны, вы уж простите великодушно мой отравленный олкаголем мозг, 80% клеток которого уже благополучно померло. Если категорически не согласны с написанным - ставьте минусы.  Будет много минусов, думаю модераторы помогут мне тему удалить . 
 
Продолжать?  Ставьте плюсеги. Закидать калом - минусики.  У нас же дерьмократия же в конце-то концов.  А я пока протрезвею и буду писать (или не писать) to be continued....
 
brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

"Семен Семеныч".... Ну код то может быть нужно было "правильно вставить". Очень много букаф, наверное познавательно (не осилил весь текст), про неонку очень правильно. И ИМХО, конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :) 

Афтар, пишы истче !

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

//конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)

Далеко не всегда так. А для ардуины - пошти всегда не так. У нас деструктор есть идеальный и универсальный - ресет.

//Сразу скажу, я вообще ни разу не профессиональный С++ программист, по образованию я - физик, по профессии - олкаголег, по хобби - дачник, по призванию и по жизни- лентяй.

У нас расхождение только по первому пункту, я какраз наоборот, не физик (хотя её люблю и уважаю).  Нипайму я Вас, зачем это. В любом учебнике еще лучше разжовано. И не все так просто. Помните: увереность любителя - предмет зависти профессионалов :) Даже в фразе "Так что первое правило класса - хранить в себе все данные своего обьекта, не завися ни от чего снаружи." не все так просто при внимательном рассмотрении реальности. Дальше не читал.  Хотя плюсону, может зелёным оно и ненавредит. А вот если бы Вы писали чего по образованию, профессии или хоби, думаю осилил бы больше.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Logik пишет:

 Помните: увереность любителя - предмет зависти профессионалов :) Даже в фразе "Так что первое правило класса - хранить в себе все данные своего обьекта, не завися ни от чего снаружи." не все так просто при внимательном рассмотрении реальности. 

То есть, первый из трех столпов ООП, инкапсуляция,  рушится "при внимательном рассмотрении реальности"?

Делайте скидку, это же Ардуня с 2 кб памяти, ну какие у нее могут быть классы, кроме простейших?

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

+! Мну одобряэ!

Хотя, имея немалый преподавательский опыт, скажу,  что учить продуктивно лишь тех, кто мотивирован, то есть сам того хочет.

А совковый подход: "все равны" и "научим всех" - наивная и вредная сказка. Дворники и водители автобусов тоже востребованы и ЗП у них - норм.

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

------------------------

А второе замечание будет уже идеологическим: подход командной разработки, со  скрытием от пользователя внутренней кухни класса -не имеет смысла в нашем контексте. У  нас всегда один разраб - он и тимлид, и архитектор и кодер и даже тестер - тоже он. Помните Этуша в "Старой-старой сказке"?

Только у нас и палач - тоже он ;) ;) ;).

Но это старый как мир спор. Как использование глобальных переменных. Вообще - не стоит, но в нашем контексте - часто бывает полезно.

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

"при внимательном рассмотрении реальности" не существует сущностей которые "не завися ни от чего снаружи." Про существование абстрактных классов Вы в курсе? Они даже не компилируются без "чего снаружи."

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Скрытие внутренней кухни - далеко не для командной разработки, а для того, чтобы своими грязными ногами не наступить на бабочку. И это правильно. Но спорить не буду, как угодно.

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

Да, wdrakula. Подпишусь под каждым словом. Тока добавлю про командную разработку. Кого в группе профи остановит protected? Даже в коде соседом писаном. Каждый отвечает за содеяное и если решил перетянуть метод из protected в public - так и будет. Ну накрайняк прожуют тему на скраме - решат надо и сделают.  Если надо конечно. Доки поправят, архитектуру подпилят (и не такое пиляли! ))). Это разработка, здесь без правок никак.  Так что это не далеко ушло от коментария /* !!!! не вызывать никогда в жизни !!!*/ ))

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

//То есть, первый из трех столпов ООП, инкапсуляция,  рушится "при внимательном рассмотрении реальности"?

Еще добавлю, как физику. Относитесь к этим столпам как к утверждению "вода кипит при 100С". Оно конечно так, но при внимательном рассмотрении есть атмосферное давление, перегретая жидкость и влияние примесей.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017
Итак, теперь мы научились хранить нужные нам данные внутри класса и пользоваться конструкторами. 
 
У функций-конструкторов есть полная противоположность - деструкторы. Эти злобные функции, как неистовые демоны ада, призваны расправляться с любым созданным классом, невзирая на его супер-полезность. Их функция - убивать, не щадя ни стар ни млад, гасить любому классу неонку, которая унутре. Вышел из зоны видимости - кончилось твое время жизни, класс!!. Добро пожаловать в Ад... Ну то-есть, для начала, в заботливые руки патологоанатома - деструктора. Деструкторы нужны, чтобы правильно освободить память выделенную классу. И если в простом до неприличия классе, таком как наш, деструктор тривиален (мы на него вообще забьём, он сам создастся, по умолчанию), то в более сложных классах, активно работающих с памятью, деструктор придется писать самим,в настоящую, кровавую врукопашную, чтоб не было потом мучительно больно за бесцельно потраченную и не освобожденную память, которая из-за этого сурово наехала на стек и Луноход-17, поэтому, улетел не на Марс, а на Солнце, где и сгорел благополучно. Ну, вопщем-то, канеш, не очень то и хотелось, денежки-то уже папилили, но осадочек остался, а вдруг бы он назад повернул, да наше супер-КБ расколотил в щебенку? Деструктор класса всегда вызывается либо автоматически, когда переменная-класс выходит из области видимости, либо вручную пограммистом, которому класс больше стал не нужен.  В терминах Ардуино это означает, что если класс глобальный, ну то есть обьявлен где-то выше всех этих ваших loop() и setup(), то время жизни класса - ровно до отключения питания. Ну или до сгорания в Солнце, а тогда вообще пофиг, освободили мы память от класса или нет. Деструктор для таких объектов автоматически нигде не вызывается. А так как наш класс-сенсор планирует жить с Ардуиной долго и, может быть, даже сначала счастливо, и умереть в один день, то и деструктор ему не нужен. Поэтому, чисто в познавательных целях приведу его синтаксис:
 
TSensor::~TSensor();
как видим, деструктор от конструктора отличается только тильдой в начале названия "~", да полным отсутствием параметров ().
 
 
Идём дальше. Что там нам еще надо в этом нашем нехитром классе аналогового сенсора?  Ааааа, значения же надо читать с пина, для которого мы так заботливо выстраивали в секции protected собственную колыбельку типа байт.  Дак мало того, что читать, его ж ведь и отдавать надо вызывающей класс основной программе, чтоб по результату опроса сумашедшего сенсора MQ-9 она решала, спать мне дальше, или зажав зубах паспорт, а под мышкой - спящего, как всегда, кота, немедленно спасаться бегством от бытового газа в запредельной концентрации. Так вставим же в обьявление класса функцию, которая будет читать значение с пина нашего датчика и отдавать всем желающим считанное значение. И незамысловато назовем её Read().  Так как аналоговый вход отдает значения от 0 до 1023, то байтом мы не отделаемся, в него всего 0-255 влазит, придется отдавать int. Хотя, отрицательных значений с аналогового пина я тоже отродясь не читал, и можно было бы обойтись типом word (ака uint16_t в терминах GCC), он как раз беззнаковый, делать этого я не буду и вот почему.  Во-первых int более привычен и понятен, во вторых наше значение туда гарантиированно влезет, правду вам говорю, 10 бит как нибуть в 16 точно поместятся. А еще как-то Оккам мне шептал в изрядном подпитии, не плодить сущностей сверх необходимости.  Или это не он бы?  Но похож... Бонусом, мы получаем возможность сигнализировать программе, что-то пошло не так, просто отдав из функции отрицательное значение, которое с аналогового pin-а в принципе прочитать нельзя. Например, я люблю отдавать -1, но это по вкусу. Итак, наша функция должна отдавать int, а принимать что?  А ничего. Все, что ей нужно для работы, а именно, номер пина, с которого читать, всё уже есть у класса унутре. (и неонка).
Поэтому, объявление функции будет выглядеть так:
// --------------------------
// .h файл

#pragma once
#include "Arduino.h"

class TSensor 
{
 protected:
  byte fpin;        // поле "номер пина" типа байт, скрытый от пользователя

 public:
  TSensor(byte apin);               // конструктор, принимающий только номер пина
  TSensor(byte apin, bool apullup); // а этот еще и включает или нет подтягивающий резистор на pin

  int Read();
}

// --------------------------
// капец .h файла

 

 
Естесственно, функция стоит в секции public, ведь она должна быть видима в основной программе. Посмотрим на реализацию.
 
//-------------------------------------
// срр файл

#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
                // чтоб знать, реализацию чего мы пишем
                 // срр файл мы не включаем в проект через #include, 
                 // поэтому #pragma once писать не надо

// Реализация нашего класса. 

// Конструктор. В конструкторе мы просто вызываем другой конструктор, более общий

TSensor::TSensor(byte apin): TSensor(apin, false) {}


// Конструктор. В конструкторе мы запоминаем номер пина, который мы ему передали в качестве
// параметра во внутреннем поле нашего класса и настраиваем его на вход с подтяжкой или без

TSensor::TSensor(byte apin, bool apullup)
{
 fpin = apin;
 if (apullup) pinMode(fpin,INPUT_PULLUP); else pinMode(fpin, INPUT);
}

int TSensor::Read(void)
{
  return analogRead(fpin);
}

// капец cpp файла
//--------------------------------------

 

 
всё. Пин прочитан, оператором return значение отдано.  Но теперь подумаем, правильно ли так делать?  В принципе, правильно, все будет работать, но от случайных помех или флюктуаций самого датчика, считанные и отданные значения могут колебаться. Бывает, датчик читает-читает значение 200, а потом ба-бах, в момент чтения приходит по шине случайная иголка, значение подскакивает до 800, сирена включается и наступает время брать под мышку сонного кота и лихорадочно думать "Куды бечь?". Чтоб этого не случалось, я всегда читаю пин несколько раз и усредняю полученные значения. Читаю ровно степеньдвойки раз, конкретно 8 или 16, чтоб среднее значение получалось простым сдвигом вправо на 3 или 4 бита.  И еще, я не колдую над битами в порту, не читаю напрямую АЦП, я знаю, что это круто, но для моих задач достаточно штатных Ардуиновских функций digitalWrite(), digitalRead(), analogRead(). В самом деле, если фоторезистор опрашивается раз в 5 секунд, какая мне разница, сколько времени будет работать analogRead() 130 микросекунд или 2000, в любом случае, разность измеряется тысячами раз, мне этого хватает. Значить, мы будем читать analogRead()-ом 8 или 16 раз, по вкусу, а потом просто отдавать это значение, сдвинутое на 3 или 4 бита вправо (то есть деленное на 8 или на 16). Ибо, сдвиги выполняются быстрее самого оптимизированного целочисленного деления на 10, допустим.   Примерно так: 
 
int TSensor::Read(void)
{
// заводим переменную с результатом, инициализируя ее нулем

  int result = 0;

// в цикле считываем с пина 16 значений, складывая с результатом

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

// делим result на 16, (получаем среднее), отдаем результат вызывающему

  return (result >> 4); 
}
теперь результат более надежен и предсказуем. Можно конечно выдумать и более сложный лагаритм усреднения, но мне реально достаточно и такого. Про то какие здесь могут возникнуть ошибки, и подводные валуны, мы поговорим позже, когда захочем, чтобы код работал надежно. Итог сегодняшнего дня: 
 
// --------------------------
// .h файл

#pragma once
#include "Arduino.h"

class TSensor 
{
 protected:
  byte fpin;        // поле "номер пина" типа байт, скрытый от пользователя

 public:
  TSensor(byte apin);               // конструктор, принимающий только номер пина
  TSensor(byte apin, bool apullup); // а этот еще и включает или нет подтягивающий резистор на pin

  int Read();     // читать данные с сенсора 
}

// --------------------------
// капец .h файла

//-------------------------------------
// срр файл

#include "TSensor.h"    // перво-наперво подключаем свой же файл определений
                // чтоб знать, реализацию чего мы пишем
                 // срр файл мы не включаем в проект через #include, 
                 // поэтому #pragma once писать не надо

// Реализация нашего класса. 

// Конструктор. В конструкторе мы просто вызываем другой конструктор, более общий

TSensor::TSensor(byte apin): TSensor(apin, false) {}


// Конструктор. В конструкторе мы запоминаем номер пина, который мы ему передали в качестве
// параметра во внутреннем поле нашего класса и настраиваем его на вход с подтяжкой или без

TSensor::TSensor(byte apin, bool apullup)
{
 fpin = apin;
 if (apullup) pinMode(fpin,INPUT_PULLUP); else pinMode(fpin, INPUT);
}

int TSensor::Read(void)
{
  int result = 0;
  for (byte i=0; i<16; i++) result += analogRead(fpin);
  return (result >> 4); 
}

// капец cpp файла
//--------------------------------------

 

проверьте, кому не лень, это сейчас уже компилируется и реально работает. Всего то нужно в макетку вонзить фоторезистор, подключить его к аналоговому pin, а своем проекте вставить 
#include "TSensor.h", и обьявить глобально  
 
TSensor MyPhoto(пин в куда он подключен, нужна/не нужна подтяжка true или false);
 
для фоторезистора, если он подключен с входа прямо на землю - нужна однозначно. 
потом в лупе почитать, но не слишком часто, 
 
value = MyPhoto.Read();
 
и повыводить это в сериал
 
Serial.print("Sensor value = "); Serial.println(value);
 
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

to be continued.  (наерна)

Вечер пришол. Пора синячить. :) 

b707
Offline
Зарегистрирован: 26.05.2017

ну Деда, ты и написал... вечером почитаю.

По поводу поговорить - мы с тобой, оказывается, совсем разные люди :) Общая у нас только лень :))))

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Logik пишет:

//конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)

Далеко не всегда так. А для ардуины - пошти всегда не так. У нас деструктор есть идеальный и универсальный - ресет.

Вот уж неправда ваша, как раз мелкопроцессоры страдают ограниченым объемом памяти и убийство объекта вполне себе необходимое дело для ардуино. А "обресетиться" можно в любой системе, иными словами, ножка ресета есть практически у любой системы имеющей хоть один триггер.

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

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

DetSimen пишет:

есть еще два, неявных конструктора
TSensor PhotoSensor; // датчик на нулевом pin, подтяжка ХЗ в каком состоянии

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DetSimen пишет:

Чтобы создать свой класс перво-наперво надо создать 2 файла, заголовочный .h файл и файл  реализации .срр

а, если я создам чужой класс и не буду создавать 2-х файлов, то что?..

если пох, чей это класс и в каком файле прописан текст ега кода?

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

brokly пишет:

Logik пишет:

//конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)

Далеко не всегда так. А для ардуины - пошти всегда не так. У нас деструктор есть идеальный и универсальный - ресет.

Вот уж неправда ваша, как раз мелкопроцессоры страдают ограниченым объемом памяти и убийство объекта вполне себе необходимое дело для ардуино. А "обресетиться" можно в любой системе, иными словами, ножка ресета есть практически у любой системы имеющей хоть один триггер.

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

Та причем тут ориентация на зеленых )) Дело в том что в МК классы создаются при запуске и остаются до победного ресета. А на ПК есть завершение проги, закрытие окна и пр. вещи предполагающие освобождение ресурсов. На МК такое как правило отсутствует напрочь. Просто нет смысла чего освобождать, даже если оно уже (а чаще пока) не нужно.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

Logik пишет:

Дело в том что в МК классы создаются при запуске и остаются до победного ресета. А на ПК есть завершение проги, закрытие окна и пр. вещи предполагающие освобождение ресурсов. На МК такое как правило отсутствует напрочь. Просто нет смысла чего освобождать, даже если оно уже (а чаще пока) не нужно.

ну, вот зачем гордо нести ересь в массы?

классам не известно на МК или ПК запущен их код и их поведение описано в коде пейсателем кода - если нужно освобождать ресурсы, то достаточно это прописать в коде - явно или неявно будет вызван деструктор и память освободится.

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

про завершение проги и закрытие окон - даже комментировать не желаю...

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

brokly пишет:
Вот уж неправда ваша, как раз мелкопроцессоры страдают ограниченым объемом памяти и убийство объекта вполне себе необходимое дело для ардуино. А "обресетиться" можно в любой системе, иными словами, ножка ресета есть практически у любой системы имеющей хоть один триггер.

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

Прежде классы это инструмент(почти как молоток), а скорее расходный материал(как гвозди). Если класс пристыкован к пинам, то анигилировать их не надо. Так как пины все равно к чему-то будут припаяны. И это надо чем-то обслуживать. Если классы это данные, или некие движки чегось. То анигиляцию их необходимо предусмотреть. Но вопросы и ответы которые я преследую на форуме, это использование классов в написании простеньких скетчей. Метод ускорения работы в написании программы и последующей доработки уже написаных программ.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

qwone пишет:

Если класс пристыкован к пинам, то анигилировать их не надо. Так как пины все равно к чему-то будут припаяны. И это надо чем-то обслуживать.

а, если не припаяны, а на разъёмах и обслуживается фактом физического отсоединения? О_О

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

brokly пишет:

И то, что в ардуино полно классов без деструкторов...

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

суки зелёные. О_О

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Клапауций 089 пишет:
а, если не припаяны, а на разъёмах и обслуживается фактом физического отсоединения? О_О
Вот когда будут программно отсоединятся, тогда и можно делать деструкторы на классы облуживающие эти пины. Как вариант вы повесите всякие мультиплексоры. Хотя в этом случае вы будете делать классы ослуживающие не пины, а пины мультиплесоров. 

ПС: Была у меня задумка организовать классы,которые создаются и уничтожаются по командам из Serial. Удаленный доступ с "удаленым программированием".

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

qwone пишет:

Вот когда будут программно отсоединятся, тогда и можно делать деструкторы на классы облуживающие эти пины. Как вариант вы повесите всякие мультиплексоры. Хотя в этом случае вы будете делать классы ослуживающие не пины, а пины мультиплесоров. 

прости, мне сложно преодолеть языковой барьер между русским и русским - я сказал ровно то, что сказал: класс утилизируется по факту физического отключения, подключенного к МК устройства. и, аналогично, создаётся - по факту физического подключения.

мой пост не нужно коммментровать, т.к. он не содержит ложных утверждений.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Я похоже еще не встречал умные пины, которые могут определить что к ним что-то физически подключено, или отключено. Так что извините.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

qwone пишет:

Я похоже еще не встречал умные пины, которые могут определить что к ним что-то физически подключено, или отключено. Так что извините.

digitalRead()

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

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

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

Logik пишет:

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

Логик, прекращай нести несвязный бред. при чём здесь периферия, если конструктор и деструктор вызываются всегда, даже, если явно не описаны в коде класса. О_О

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

qwone пишет:

Была у меня задумка организовать классы,которые создаются и уничтожаются по командам из Serial. Удаленный доступ с "удаленым программированием".

Это совершенно не сложно технически. Свичь по принятым с сириала данным. В его кейсах - конструкторы с деструкторами. Только вот зачем?! Это может быть интересно если есть несколько классов отвечающих требованиям: 1. Исключается их паралельная работа, работают сеансами. 2. Каждый сеанс работы обекта класса  не зависит (мало зависит) от предыдущего. Т.е. данных между сеансами почти нет. 3.Классы используют много ОЗУ ( иначе нет смысла освобождать ) но не крупноблочно (иначе можно просто блок ОЗУ  передавать между ними). 4.Но при п.3 характер использование ОЗУ не ведет к фрагментации. Что вобщем весьма противоречиво. 5. классы образуют иерархию - как условие что классы вобще нужно задействовать. Напрягая фантазию я не вижу применения.

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

Клапауций 089 пишет:

 при чём здесь периферия.

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Ну почему. Берешь панель Nextion. Но не сам ее прошиваешь, а пишешь загрузчик через флешку. Потом берешь Ардуину со скетчем готовых классов, конструкторами и деструкторами. Осталось только во флешку прописать где какой пин за какой класс отвечает и готово. Разумеется для более менее шарящих это дичь. Но комерческий проект, что бы взять с новичков лишний баблос, но не пытаться их обучать программированию шикарно. Как же на одной сменной флешке программа и для дисплея и для внешней Ардуины. А там и внешнюю Ардуину на STM модуль можно поменять.

Пишешь в Serial Create Led1 2 и подключен светодиод на пин 2  Led1 ON - он включен . Можно и кнопку(сенсор) также подключить

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

Logik пишет:

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

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

класс может не работать с железом и ему пофиг на инициализацию железа.

*ещё раз повторяю - уймись, ты серешь.

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

Клапауций 089 пишет:

Logik пишет:

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

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

класс может не работать с железом и ему пофиг на инициализацию железа.

*ещё раз повторяю - уймись, ты серешь.

Бугага! Так для МК работа с железом - типичнейшое! Не ряды Фибоначи же расчитывать! А для ПК кстати работа с железо это действительно частный случай. Круг мысли вобщем замкнулся - что гуд на ПК, то яд для МК.

 

 

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

Logik пишет:

Бугага! Так для МК работа с железом - типичнейшое! Не ряды Фибоначи же расчитывать!

if (типичнейшое != единственное) {Логик идёт на Север учить рюзке изык;}

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

qwone пишет:

Пишешь в Serial Create Led1 2 и подключен светодиод на пин 2  Led1 ON - он включен . Можно и кнопку(сенсор) также подключить

Так тут классы и близко не нужны. Просто протокол чтоб Create Led1 2  и Led1 ON и пр. передавать/принимать/обрабатывать. Освобождать нечего, иерархии нет.. Просто набор обработчиков разных.

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

Клапауций 089 пишет:

Logik пишет:

Бугага! Так для МК работа с железом - типичнейшое! Не ряды Фибоначи же расчитывать!

if (типичнейшое != единственное) {Логик идёт на Север учить рюзке изык;}

А Клапауций  просто идет нах, как разминка перед очередным переименованием и баном.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

обработчики и создают объекты по классам, передают им команды, и по командt удаляют delete Led1. И все объект удален , память и пин свобоны.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

Logik пишет:

А Клапауций  просто идет нах, как разминка перед очередным переименованием и баном.

кстати, а почему атмины не забанят это дурное хамло?

оно какую-то пользу форуму, кроме срачей, приносит, выскакивая как чёртик из коробочки, вводя ползателей форума в заблуждение тупым бредом?

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

Клапауций 089 пишет:

Logik пишет:

А Клапауций  просто идет нах, как разминка перед очередным переименованием и баном.

кстати, а почему атмины не забанят это дурное хамло?

оно какую-то пользу форуму, кроме срачей, приносит, выскакивая как чёртик из коробочки, вводя ползателей форума в заблуждение тупым бредом?

ты, как истинный "ползатель" форума отправлен на заслуженое месте.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

 

здесь #14, походу, оскорбил чьи-то чуйства...

так, вот - я это сделал специально. О_О


class test {}; // ой! а, где все эти #include, *.h, *.срр, поля и методы?

// мама, мама, что я буду делать?

void setup() {}

void loop() {}

 

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

qwone пишет:

обработчики и создают объекты по классам, передают им команды, и по командt удаляют delete Led1. И все объект удален , память и пин свобоны.

Ну и что реально будет при создании класса? pinMode. А в освобождении? ну вобще ничего. А в единственном методе? digitalWrite. Нафига вся писанина с классами если и без них проще некуда.  И ОЗУ вобще не освободим ибо не занимали.

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

Клапауций 089 пишет:

 

здесь #14, походу, оскорбил чьи-то чуйства...

так, вот - я это сделал специально. О_О


class test {}; // ой! а, где все эти #include, *.h, *.срр, поля и методы?

// мама, мама, что я буду делать?

void setup() {}

void loop() {}

 

Химия или чего растительного принял? )))

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Чота, да. По трезвому размышлению я понял, что опять, в который раз уйню сморозил, ну как обычно.  Новичкам это всё равно филькина грамота, они в условиях if-то путаюца, а им тут про классы втирают.  А остальные и так всё знают, гораздо лучше меня.  

Тему, наерна, можно не удалять, если в ней срач не разводить, она с главной сама уйдет и заглохнет.  Это на усмотрение модераторов. 

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

DetSimen пишет:

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

Нормально, если потереть срач, то очень даже полезно.
Я новичек но if от else отличаю. А вот классы и написание библиотек это высший пилотаж для меня поэтому тема полезна.

Просьба модераторам потереть срач.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

Andrey12 пишет:

А вот классы и написание библиотек

дык, в том то и дело - смешались в кучу... классы и библиотеки.

начинать с этого:

class test {};

void setup() {}

void loop() {}

иначе, неофит не поймёт, зачем эти все нагромождения инклюдов ccp и h

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 089 пишет:

иначе, неофит не поймёт, зачем эти все нагромождения инклюдов ccp и h

Неофит не поймёт, зачем вообще классы нужны :)

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DIYMan пишет:

Клапауций 089 пишет:

иначе, неофит не поймёт, зачем эти все нагромождения инклюдов ccp и h

Неофит не поймёт, зачем вообще классы нужны :)

верно - нужно объяснить, зачем вообще эти танцы с бубнами, а не плодить последователей карго-культа - сделай вот так и так и прилетят белые на вертолёте и сбросят ящик тушёнки.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

например - лично я так и не понять суть обязательного разбивания кода класса на файлы *.h и *.cpp

ни в одном обучающем примере я не нашёл - зачем это обязательно нужно.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 089 пишет:

например - лично я так и не понять суть обязательного разбивания кода класса на файлы *.h и *.cpp

ни в одном обучающем примере я не нашёл - зачем это обязательно нужно.

Обязательно это не нужно :) Просто с точки зрения архитектуры сложного приложения - в *.h-файле объявления (и частичная имплементация, если надо), в *.cpp - реализация. H-файл в этом случае - как меню в кафе: на кухне много блюд, но заказать быстрее, пролистав меню, если угодна такая аналогия.

Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DIYMan пишет:

Обязательно это не нужно :)

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

DIYMan пишет:

Просто с точки зрения архитектуры сложного приложения - в *.h-файле объявления (и частичная имплементация, если надо), в *.cpp - реализация. H-файл в этом случае - как меню в кафе: на кухне много блюд, но заказать быстрее, пролистав меню, если угодна такая аналогия.

С точки зрения банальной эрудиции, каждый здравомыслящий индивидуум, теоритически модифицирующий научную абстракцию, не может игнорировать тенденцию парадоксальных иллюзий. О_О
 
DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 089 пишет:

 

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

Лично для меня, подчёркиваю, лично для меня - это аргументация. Ибо я активно применяю разделение на разные единицы в больших проектах - будь то разделение на классы или раскидывание функционала на *.h/*.cpp. 
 
Есть ещё один аргумент: повторное использование. Когда у тебя код кнопки лежит в отдельных файлах - это грамотней, чем каждый раз копипастой заниматься ;) - достаточно подключить *.h-файл. И это, заметь - ни разу не про "библиотеки" (обозвали же млять библиотеками набор исходников - вот кого надо драть).
Клапауций 089
Клапауций 089 аватар
Offline
Зарегистрирован: 14.01.2018

DIYMan пишет:

Лично для меня, подчёркиваю, лично для меня - это аргументация. Ибо я активно применяю разделение на разные единицы в больших проектах - будь то разделение на классы или раскидывание функционала на *.h/*.cpp. 
 
 
ок. я понял - это вопрос традиций, смысл которых канул в века.
 
DIYMan пишет:
 
Есть ещё один аргумент: повторное использование. Когда у тебя код кнопки лежит в отдельных файлах - это грамотней, чем каждый раз копипастой заниматься ;) - достаточно подключить *.h-файл. И это, заметь - ни разу не про "библиотеки" (обозвали же млять библиотеками набор исходников - вот кого надо драть).

у меня файл кода класса кнопки лежит в отдельном файле-*.h

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 089 пишет:

у меня файл кода класса кнопки лежит в отдельном файле-*.h

У тебя просто частный случай, когда весь код можно поместить в *.h-файл. Не всегда это возможно и удобно, хотя бы из-за объёма кода. Но такой вариант, безусловно, применим, и очень часто встречается на практике. Но, подчеркну ещё раз - этим вариантом всего не объять, именно поэтому *.cpp-файлы имеют место быть.