Ах, эти страшные, сложные, милые, простые классы.
- Войдите на сайт для отправки комментариев
Втр, 16/01/2018 - 08:46
Эпиграф: Не бойтесь писать классами... (ударение каждый поставит где нраица)
Это типа эссе для новичков, зеленых новичков и новичков вапще с ног до головы облитых зелёнкой. Главное в новичке - любознательность и желание задумываться и учиться, а не уровень громкость вопля "ПАМАГИТИ!!!!" на форуме.
Сразу скажу, я вообще ни разу не профессиональный С++ программист, по образованию я - физик, по профессии - олкаголег, по хобби - дачник, по призванию и по жизни- лентяй. Последнее я умею делать профессионально (сам удивляюсь, как до полтинника дожил и жена не убила).
Это просто попытка такого же, как и вы, полудилетанта, рассказать простыми словами, что не надо бояться классов, не так страшен квадрат, как его Малевич. (на самом деле надо бояться классов, но только таких, как у 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....
"Семен Семеныч".... Ну код то может быть нужно было "правильно вставить". Очень много букаф, наверное познавательно (не осилил весь текст), про неонку очень правильно. И ИМХО, конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)
Афтар, пишы истче !
//конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)
Далеко не всегда так. А для ардуины - пошти всегда не так. У нас деструктор есть идеальный и универсальный - ресет.
//Сразу скажу, я вообще ни разу не профессиональный С++ программист, по образованию я - физик, по профессии - олкаголег, по хобби - дачник, по призванию и по жизни- лентяй.
У нас расхождение только по первому пункту, я какраз наоборот, не физик (хотя её люблю и уважаю). Нипайму я Вас, зачем это. В любом учебнике еще лучше разжовано. И не все так просто. Помните: увереность любителя - предмет зависти профессионалов :) Даже в фразе "Так что первое правило класса - хранить в себе все данные своего обьекта, не завися ни от чего снаружи." не все так просто при внимательном рассмотрении реальности. Дальше не читал. Хотя плюсону, может зелёным оно и ненавредит. А вот если бы Вы писали чего по образованию, профессии или хоби, думаю осилил бы больше.
Помните: увереность любителя - предмет зависти профессионалов :) Даже в фразе "Так что первое правило класса - хранить в себе все данные своего обьекта, не завися ни от чего снаружи." не все так просто при внимательном рассмотрении реальности.
То есть, первый из трех столпов ООП, инкапсуляция, рушится "при внимательном рассмотрении реальности"?
Делайте скидку, это же Ардуня с 2 кб памяти, ну какие у нее могут быть классы, кроме простейших?
+! Мну одобряэ!
Хотя, имея немалый преподавательский опыт, скажу, что учить продуктивно лишь тех, кто мотивирован, то есть сам того хочет.
А совковый подход: "все равны" и "научим всех" - наивная и вредная сказка. Дворники и водители автобусов тоже востребованы и ЗП у них - норм.
В нашем случае, на форуме, если человек задает вопрос,ответ на который гуглится за пять минут, то гуманнее сразу сказать ему, что он муд.. и послать на...!
------------------------
А второе замечание будет уже идеологическим: подход командной разработки, со скрытием от пользователя внутренней кухни класса -не имеет смысла в нашем контексте. У нас всегда один разраб - он и тимлид, и архитектор и кодер и даже тестер - тоже он. Помните Этуша в "Старой-старой сказке"?
Только у нас и палач - тоже он ;) ;) ;).
Но это старый как мир спор. Как использование глобальных переменных. Вообще - не стоит, но в нашем контексте - часто бывает полезно.
"при внимательном рассмотрении реальности" не существует сущностей которые "не завися ни от чего снаружи." Про существование абстрактных классов Вы в курсе? Они даже не компилируются без "чего снаружи."
Скрытие внутренней кухни - далеко не для командной разработки, а для того, чтобы своими грязными ногами не наступить на бабочку. И это правильно. Но спорить не буду, как угодно.
Да, wdrakula. Подпишусь под каждым словом. Тока добавлю про командную разработку. Кого в группе профи остановит protected? Даже в коде соседом писаном. Каждый отвечает за содеяное и если решил перетянуть метод из protected в public - так и будет. Ну накрайняк прожуют тему на скраме - решат надо и сделают. Если надо конечно. Доки поправят, архитектуру подпилят (и не такое пиляли! ))). Это разработка, здесь без правок никак. Так что это не далеко ушло от коментария /* !!!! не вызывать никогда в жизни !!!*/ ))
//То есть, первый из трех столпов ООП, инкапсуляция, рушится "при внимательном рассмотрении реальности"?
Еще добавлю, как физику. Относитесь к этим столпам как к утверждению "вода кипит при 100С". Оно конечно так, но при внимательном рассмотрении есть атмосферное давление, перегретая жидкость и влияние примесей.
to be continued. (наерна)
Вечер пришол. Пора синячить. :)
ну Деда, ты и написал... вечером почитаю.
По поводу поговорить - мы с тобой, оказывается, совсем разные люди :) Общая у нас только лень :))))
//конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)
Далеко не всегда так. А для ардуины - пошти всегда не так. У нас деструктор есть идеальный и универсальный - ресет.
Вот уж неправда ваша, как раз мелкопроцессоры страдают ограниченым объемом памяти и убийство объекта вполне себе необходимое дело для ардуино. А "обресетиться" можно в любой системе, иными словами, ножка ресета есть практически у любой системы имеющей хоть один триггер.
И то, что в ардуино полно классов без деструкторов, связано как раз с ориентацией этой системы на "зеленых", которые не знают, что в отличии от физических объектов (природных), логические объекты (программные), могут рождаться из ничего и туда же аннигилироваться.
есть еще два, неявных конструктора
TSensor PhotoSensor; // датчик на нулевом pin, подтяжка ХЗ в каком состоянии
Чтобы создать свой класс перво-наперво надо создать 2 файла, заголовочный .h файл и файл реализации .срр
а, если я создам чужой класс и не буду создавать 2-х файлов, то что?..
если пох, чей это класс и в каком файле прописан текст ега кода?
//конструктор без деструктора, как водка без закуски - вроде есть, но не вкусно :)
Далеко не всегда так. А для ардуины - пошти всегда не так. У нас деструктор есть идеальный и универсальный - ресет.
Вот уж неправда ваша, как раз мелкопроцессоры страдают ограниченым объемом памяти и убийство объекта вполне себе необходимое дело для ардуино. А "обресетиться" можно в любой системе, иными словами, ножка ресета есть практически у любой системы имеющей хоть один триггер.
И то, что в ардуино полно классов без деструкторов, связано как раз с ориентацией этой системы на "зеленых", которые не знают, что в отличии от физических объектов (природных), логические объекты (программные), могут рождаться из ничего и туда же аннигилироваться.
Та причем тут ориентация на зеленых )) Дело в том что в МК классы создаются при запуске и остаются до победного ресета. А на ПК есть завершение проги, закрытие окна и пр. вещи предполагающие освобождение ресурсов. На МК такое как правило отсутствует напрочь. Просто нет смысла чего освобождать, даже если оно уже (а чаще пока) не нужно.
Дело в том что в МК классы создаются при запуске и остаются до победного ресета. А на ПК есть завершение проги, закрытие окна и пр. вещи предполагающие освобождение ресурсов. На МК такое как правило отсутствует напрочь. Просто нет смысла чего освобождать, даже если оно уже (а чаще пока) не нужно.
ну, вот зачем гордо нести ересь в массы?
классам не известно на МК или ПК запущен их код и их поведение описано в коде пейсателем кода - если нужно освобождать ресурсы, то достаточно это прописать в коде - явно или неявно будет вызван деструктор и память освободится.
если создаёшь многофункциональный девайс, исполняющий разные функции - как же нет смысла освобождать память при переключении между режимами?
про завершение проги и закрытие окон - даже комментировать не желаю...
И то, что в ардуино полно классов без деструкторов, связано как раз с ориентацией этой системы на "зеленых", которые не знают, что в отличии от физических объектов (природных), логические объекты (программные), могут рождаться из ничего и туда же аннигилироваться.
Если класс пристыкован к пинам, то анигилировать их не надо. Так как пины все равно к чему-то будут припаяны. И это надо чем-то обслуживать.
а, если не припаяны, а на разъёмах и обслуживается фактом физического отсоединения? О_О
И то, что в ардуино полно классов без деструкторов...
копай глубже - и, без конструкторов... но чудесным образом создаются и утилизируются.
суки зелёные. О_О
ПС: Была у меня задумка организовать классы,которые создаются и уничтожаются по командам из Serial. Удаленный доступ с "удаленым программированием".
Вот когда будут программно отсоединятся, тогда и можно делать деструкторы на классы облуживающие эти пины. Как вариант вы повесите всякие мультиплексоры. Хотя в этом случае вы будете делать классы ослуживающие не пины, а пины мультиплесоров.
прости, мне сложно преодолеть языковой барьер между русским и русским - я сказал ровно то, что сказал: класс утилизируется по факту физического отключения, подключенного к МК устройства. и, аналогично, создаётся - по факту физического подключения.
мой пост не нужно коммментровать, т.к. он не содержит ложных утверждений.
Я похоже еще не встречал умные пины, которые могут определить что к ним что-то физически подключено, или отключено. Так что извините.
Я похоже еще не встречал умные пины, которые могут определить что к ним что-то физически подключено, или отключено. Так что извините.
digitalRead()
Кстати про "и без конструкторов" Думаю зелёным надо уяснить, что отсутствие конструкторов является оправданным т.к. для глобальных обектов конструкторы будут вызыватся на неиниченой переферии. А для динамических обектов - нет. Чем в значительной части и вызвано их отсутствие и наличие натоместь всяких бигинов и инитов. Кто там вякал про "классам не известно на МК или ПК запущен их код"? Вот еще одно отличие. Гы! да это шут месный вякал... Ну ему можна любую пургу нести.. Потому что Клапауций..
Кстати про "и без конструкторов" Думаю зелёным надо уяснить, что отсутствие конструкторов является оправданным т.к. для глобальных обектов конструкторы будут вызыватся на неиниченой переферии. А для динамических обектов - нет. Чем в значительной части и вызвано их отсутствие и наличие натоместь всяких бигинов и инитов. Кто там вякал про "классам не известно на МК или ПК запущен их код"? Вот еще одно отличие. Гы! да это шут месный вякал... Ну ему можна любую пургу нести.. Потому что Клапауций..
Логик, прекращай нести несвязный бред. при чём здесь периферия, если конструктор и деструктор вызываются всегда, даже, если явно не описаны в коде класса. О_О
Была у меня задумка организовать классы,которые создаются и уничтожаются по командам из Serial. Удаленный доступ с "удаленым программированием".
Это совершенно не сложно технически. Свичь по принятым с сириала данным. В его кейсах - конструкторы с деструкторами. Только вот зачем?! Это может быть интересно если есть несколько классов отвечающих требованиям: 1. Исключается их паралельная работа, работают сеансами. 2. Каждый сеанс работы обекта класса не зависит (мало зависит) от предыдущего. Т.е. данных между сеансами почти нет. 3.Классы используют много ОЗУ ( иначе нет смысла освобождать ) но не крупноблочно (иначе можно просто блок ОЗУ передавать между ними). 4.Но при п.3 характер использование ОЗУ не ведет к фрагментации. Что вобщем весьма противоречиво. 5. классы образуют иерархию - как условие что классы вобще нужно задействовать. Напрягая фантазию я не вижу применения.
при чём здесь периферия.
Тебе хоть плюй в глаза - все божя роса. Глобальный обект, с конструктором вызываным до инита переферии может делать что угодно, настраивать шины, таймеры и пр. все это будет стерто при системном интите переферии. И класс просто не будет работать с криво настроеным железом. А динамический обект того же класса, созданый из сетапа и позже насторит переферию верно, уже после её системного инита, и работать будет. Так что пшелвон, матчасть учить.
Ну почему. Берешь панель Nextion. Но не сам ее прошиваешь, а пишешь загрузчик через флешку. Потом берешь Ардуину со скетчем готовых классов, конструкторами и деструкторами. Осталось только во флешку прописать где какой пин за какой класс отвечает и готово. Разумеется для более менее шарящих это дичь. Но комерческий проект, что бы взять с новичков лишний баблос, но не пытаться их обучать программированию шикарно. Как же на одной сменной флешке программа и для дисплея и для внешней Ардуины. А там и внешнюю Ардуину на STM модуль можно поменять.
Пишешь в Serial Create Led1 2 и подключен светодиод на пин 2 Led1 ON - он включен . Можно и кнопку(сенсор) также подключить
Тебе хоть плюй в глаза - все божя роса. Глобальный обект, с конструктором вызываным до инита переферии может делать что угодно, настраивать шины, таймеры и пр. все это будет стерто при системном интите переферии. И класс просто не будет работать с криво настроеным железом. А динамический обект того же класса, созданый из сетапа и позже насторит переферию верно, уже после её системного инита, и работать будет. Так что пшелвон, матчасть учить.
ну, и нафига частный случай, когда класс работает с железом, объявлять истиной, применимой ко всем классам?
класс может не работать с железом и ему пофиг на инициализацию железа.
*ещё раз повторяю - уймись, ты серешь.
Тебе хоть плюй в глаза - все божя роса. Глобальный обект, с конструктором вызываным до инита переферии может делать что угодно, настраивать шины, таймеры и пр. все это будет стерто при системном интите переферии. И класс просто не будет работать с криво настроеным железом. А динамический обект того же класса, созданый из сетапа и позже насторит переферию верно, уже после её системного инита, и работать будет. Так что пшелвон, матчасть учить.
ну, и нафига частный случай, когда класс работает с железом, объявлять истиной, применимой ко всем классам?
класс может не работать с железом и ему пофиг на инициализацию железа.
*ещё раз повторяю - уймись, ты серешь.
Бугага! Так для МК работа с железом - типичнейшое! Не ряды Фибоначи же расчитывать! А для ПК кстати работа с железо это действительно частный случай. Круг мысли вобщем замкнулся - что гуд на ПК, то яд для МК.
Бугага! Так для МК работа с железом - типичнейшое! Не ряды Фибоначи же расчитывать!
if (типичнейшое != единственное) {Логик идёт на Север учить рюзке изык;}
Пишешь в Serial Create Led1 2 и подключен светодиод на пин 2 Led1 ON - он включен . Можно и кнопку(сенсор) также подключить
Так тут классы и близко не нужны. Просто протокол чтоб Create Led1 2 и Led1 ON и пр. передавать/принимать/обрабатывать. Освобождать нечего, иерархии нет.. Просто набор обработчиков разных.
Бугага! Так для МК работа с железом - типичнейшое! Не ряды Фибоначи же расчитывать!
if (типичнейшое != единственное) {Логик идёт на Север учить рюзке изык;}
А Клапауций просто идет нах, как разминка перед очередным переименованием и баном.
обработчики и создают объекты по классам, передают им команды, и по командt удаляют delete Led1. И все объект удален , память и пин свобоны.
А Клапауций просто идет нах, как разминка перед очередным переименованием и баном.
кстати, а почему атмины не забанят это дурное хамло?
оно какую-то пользу форуму, кроме срачей, приносит, выскакивая как чёртик из коробочки, вводя ползателей форума в заблуждение тупым бредом?
А Клапауций просто идет нах, как разминка перед очередным переименованием и баном.
кстати, а почему атмины не забанят это дурное хамло?
оно какую-то пользу форуму, кроме срачей, приносит, выскакивая как чёртик из коробочки, вводя ползателей форума в заблуждение тупым бредом?
ты, как истинный "ползатель" форума отправлен на заслуженое месте.
здесь #14, походу, оскорбил чьи-то чуйства...
так, вот - я это сделал специально. О_О
обработчики и создают объекты по классам, передают им команды, и по командt удаляют delete Led1. И все объект удален , память и пин свобоны.
Ну и что реально будет при создании класса? pinMode. А в освобождении? ну вобще ничего. А в единственном методе? digitalWrite. Нафига вся писанина с классами если и без них проще некуда. И ОЗУ вобще не освободим ибо не занимали.
здесь #14, походу, оскорбил чьи-то чуйства...
так, вот - я это сделал специально. О_О
Химия или чего растительного принял? )))
Чота, да. По трезвому размышлению я понял, что опять, в который раз уйню сморозил, ну как обычно. Новичкам это всё равно филькина грамота, они в условиях if-то путаюца, а им тут про классы втирают. А остальные и так всё знают, гораздо лучше меня.
Тему, наерна, можно не удалять, если в ней срач не разводить, она с главной сама уйдет и заглохнет. Это на усмотрение модераторов.
Чота, да. По трезвому размышлению я понял, что опять, в который раз уйню сморозил, ну как обычно. Новичкам это всё равно филькина грамота, они в условиях if-то путаюца, а им тут про классы втирают.
Нормально, если потереть срач, то очень даже полезно.
Я новичек но if от else отличаю. А вот классы и написание библиотек это высший пилотаж для меня поэтому тема полезна.
Просьба модераторам потереть срач.
А вот классы и написание библиотек
дык, в том то и дело - смешались в кучу... классы и библиотеки.
начинать с этого:
иначе, неофит не поймёт, зачем эти все нагромождения инклюдов ccp и h
иначе, неофит не поймёт, зачем эти все нагромождения инклюдов ccp и h
Неофит не поймёт, зачем вообще классы нужны :)
иначе, неофит не поймёт, зачем эти все нагромождения инклюдов ccp и h
Неофит не поймёт, зачем вообще классы нужны :)
верно - нужно объяснить, зачем вообще эти танцы с бубнами, а не плодить последователей карго-культа - сделай вот так и так и прилетят белые на вертолёте и сбросят ящик тушёнки.
например - лично я так и не понять суть обязательного разбивания кода класса на файлы *.h и *.cpp
ни в одном обучающем примере я не нашёл - зачем это обязательно нужно.
например - лично я так и не понять суть обязательного разбивания кода класса на файлы *.h и *.cpp
ни в одном обучающем примере я не нашёл - зачем это обязательно нужно.
Обязательно это не нужно :) Просто с точки зрения архитектуры сложного приложения - в *.h-файле объявления (и частичная имплементация, если надо), в *.cpp - реализация. H-файл в этом случае - как меню в кафе: на кухне много блюд, но заказать быстрее, пролистав меню, если угодна такая аналогия.
Обязательно это не нужно :)
вот! это не обязательно. а, когда - обязательно?.. и, тут начинается то, что ты написал ниже - признайся, что это не аргументация, а что-то странное.
Просто с точки зрения архитектуры сложного приложения - в *.h-файле объявления (и частичная имплементация, если надо), в *.cpp - реализация. H-файл в этом случае - как меню в кафе: на кухне много блюд, но заказать быстрее, пролистав меню, если угодна такая аналогия.
вот! это не обязательно. а, когда - обязательно?.. и, тут начинается то, что ты написал ниже - признайся, что это не аргументация, а что-то странное.
у меня файл кода класса кнопки лежит в отдельном файле-*.h
я могу пометить код в файле или файлах с любым иным расширением или тупо копипастить - в зависимости от контекста использования кода.
у меня файл кода класса кнопки лежит в отдельном файле-*.h
У тебя просто частный случай, когда весь код можно поместить в *.h-файл. Не всегда это возможно и удобно, хотя бы из-за объёма кода. Но такой вариант, безусловно, применим, и очень часто встречается на практике. Но, подчеркну ещё раз - этим вариантом всего не объять, именно поэтому *.cpp-файлы имеют место быть.