Классы и объекты: сколько и какие типы файлов мне действительно нужно использовать?
У меня нет предыдущего опыта работы с C ++ или C, но я знаю, как программировать C # и изучать Arduino. Я просто хочу организовать свои эскизы и вполне устраиваю язык Arduino даже с его ограничениями, но я действительно хотел бы иметь объектно-ориентированный подход к программированию Arduino.
Итак, я видел, что вы можете использовать следующие способы (не исчерпывающий список) для организации кода:
- Один файл .ino;
- Несколько файлов .ino в той же папке (что IDE вызывает и отображает как «вкладки»);
- Файл .ino с включенными файлами .h и .cpp в той же папке;
- То же, что и выше, но файлы - это установленная библиотека внутри папки программы Arduino.
Я также слышал о следующих способах, но пока не получил их:
- Объявление класса C ++ в одном и том же одиночном файле .ino (слышал, но никогда не видел, что это возможно?);
- [предпочтительный подход] . Включает файл .cpp, где объявлен класс, но без с использованием .h-файла (хотелось бы, чтобы этот подход работал?);
Обратите внимание, что я хочу использовать только классы, чтобы код был более разделенным, мои приложения должны быть очень простыми, только в основном с кнопками, светодиодами и звуковыми сигналами.
3 ответа
Как среда IDE организует вещи
Во-первых, именно так IDE организует ваш «эскиз»:
- Основной файл
.ino
является тем же именем, что и папка, в которой он находится. Таким образом, дляfoobar.ino
вfoobar
- основной файл - foobar.ino. - Любые другие
.ino
файлы в этой папке объединены вместе в алфавитном порядке в конце основного файла (независимо от того, где находится основной файл, в алфавитном порядке). - Этот конкатенированный файл становится файлом
.cpp
(например.foobar.cpp
) - он помещается во временную папку компиляции. - Препроцессор «полезно» генерирует прототипы функций для функций, которые он находит в этом файле.
- Основной файл сканируется для директив
#include <libraryname>
. Это заставляет IDE также копировать все соответствующие файлы из каждой (упомянутой) библиотеки во временную папку и генерировать инструкции для их компиляции. - Любые файлы
.c
,.cpp
или.asm
в папке эскиза добавляются в процесс сборки как отдельные единицы компиляции (это есть, они скомпилированы обычным образом в виде отдельных файлов) - Любые файлы
.h
также копируются во временную папку компиляции, поэтому их можно ссылаться на ваши файлы .c или .cpp. - Компилятор добавляет в стандартные файлы процесса сборки (например,
main.cpp
) - Затем процесс сборки компилирует все вышеуказанные файлы в файлы объектов.
- Если фаза компиляции завершается успешно, они связаны вместе со стандартными библиотеками AVR (например, давая вам
strcpy
и т. д.)
Побочным эффектом всего этого является то, что основной эскиз (файлы .ino) можно рассматривать как C ++ во всех смыслах и целях. Однако генерация прототипа функции может привести к неясным сообщениям об ошибках, если вы не будете осторожны.
Избегайте предпроцессорных причуд
Самый простой способ избежать этих особенностей - оставить свой основной эскиз пустым (и не использовать другие файлы .ino
). Затем сделайте еще одну вкладку (файл .cpp
) и поместите в нее свои вещи следующим образом:
#include <Arduino.h>
// put your sketch here ...
void setup ()
{
} // end of setup
void loop ()
{
} // end of loop
Обратите внимание, что вам нужно включить Arduino.h
. IDE делает это автоматически для основного эскиза, но для других единиц компиляции вам нужно это сделать. В противном случае он не будет знать о таких вещах, как String, аппаратные регистры и т. Д.
Избегая установки /основной парадигмы
Вам не нужно запускать концепцию установки /цикла. Например, ваш .cpp-файл может быть:
#include <Arduino.h>
int main ()
{
init (); // initialize timers
Serial.begin (115200);
Serial.println ("Hello, world");
Serial.flush (); // let serial printing finish
} // end of main
Включение встроенной библиотеки
Если вы используете концепцию «пустой эскиз», вам все равно нужно включить библиотеки, используемые в другом месте проекта, например, в основной файл .ino
:
#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>
Это связано с тем, что среда IDE проверяет только основной файл для использования библиотеки. Эффективно вы можете рассматривать основной файл как «проектный» файл, который назначает, какие внешние библиотеки используются.
Проблемы с именами
-
Не называйте свой основной эскиз «main.cpp» - среда IDE включает свой собственный main.cpp, поэтому вы будете иметь дубликат, если вы это сделаете.
-
Не указывайте свой .cpp-файл с тем же именем, что и ваш основной файл .ino. Так как файл .ino эффективно становится .cpp-файлом, это также даст вам столкновение имен.
Объявление класса C ++ в одном и том же одиночном файле .ino (слышал, но никогда не видел, что это возможно?);
Да, это компилируется ОК:
class foo {
public:
};
foo bar;
void setup () { }
void loop () { }
Однако вам, вероятно, лучше всего следовать обычной практике: поместите свои объявления в файлы .h
и ваши определения (реализации) в .cpp
(или .c
).
Почему «возможно»?
Как мой пример показывает, что can помещает все вместе в один файл. Для более крупных проектов лучше быть более организованным. В конце концов вы попадаете на сцену в проекте среднего и крупного размера, где вы хотите разделить вещи на «черные ящики» - то есть класс, который делает что-то одно, делает это хорошо, проверяется и является самодостаточным ( насколько это возможно).
Если этот класс используется в нескольких других файлах в вашем проекте, это где отдельный .h
и .cpp
файлы вступают в игру.
-
.h
file объявляет класс - то есть он предоставляет достаточно подробностей для других файлов, чтобы знать, что он делает, какие функции он имеет и как они называются. -
Файл
.cpp
определяет (реализует) класс - то есть он фактически предоставляет функции и статические члены класса, которые делают класс его вещь. Поскольку вы только хотите реализовать его один раз, это находится в отдельном файле. -
Файл
.h
- это то, что входит в другие файлы. Файл.cpp
скомпилирован один раз IDE для реализации функций класса.
Библиотеки
Если вы следуете этой парадигме, вы готовы полностью перемещать весь класс (файлы .h
и .cpp
) в библиотеку. Затем его можно разделить между несколькими проектами. Все, что требуется, это создать папку (например, myLibrary
) и поместить в нее файлы .h
и .cpp
(например. myLibrary.h и myLibrary.h
), а затем поместите эту папку в папку myLibrary.cpp
в папке, где сохраняются ваши эскизы (папка с альбомными книжками) ,
Перезагрузите среду IDE, и теперь она знает об этой библиотеке. Это действительно тривиально просто, и теперь вы можете поделиться этой библиотекой по нескольким проектам. Я делаю это много.
Немного подробнее здесь .
Мой совет заключается в том, чтобы придерживаться типичного способа выполнения C ++: отдельный интерфейс и реализация в файлы .h и .cpp для каждого класса.
Есть несколько уловов:
- вам нужен хотя бы один файл .ino. Я использую символическую ссылку на файл .cpp, где я создаю классы.
- вы должны предоставить обратные вызовы, которые ожидает среда Arduino (setu, loop и т. д.).
- в некоторых случаях вас удивят нестандартные странные вещи, которые различают IDE Arduino от нормального, например, автоматическое включение определенных библиотек, но не другие.
Или вы можете вырвать IDE Arduino и попробовать Eclipse . Как я уже упоминал, некоторые из вещей, которые должны помочь новичкам, как правило, заходят на пути более опытных разработчиков.
Я отправляю ответ только для полноты, после выяснения и тестирования способа объявления и реализации класса в том же .cpp-файле без использования заголовка. Итак, в отношении точной формулировки моего вопроса «сколько типов файлов мне нужно использовать классы», в настоящем ответе используются два файла: один .ino с include, setup и loop и .cpp, содержащий целое (довольно минималистичное ) класс, представляющий сигналы поворота игрушечного автомобиля.
Blinker.ino
#include <TurnSignals.cpp>
TurnSignals turnSignals(2, 4, 8);
void setup() { }
void loop() {
turnSignals.run();
}
TurnSignals.cpp
#include "Arduino.h"
class TurnSignals
{
int
_left,
_right,
_buzzer;
const int
amberPeriod = 300,
beepInFrequency = 600,
beepOutFrequency = 500,
beepDuration = 20;
boolean
lightsOn = false;
public : TurnSignals(int leftPin, int rightPin, int buzzerPin)
{
_left = leftPin;
_right = rightPin;
_buzzer = buzzerPin;
pinMode(_left, OUTPUT);
pinMode(_right, OUTPUT);
pinMode(_buzzer, OUTPUT);
}
public : void run()
{
blinkAll();
}
void blinkAll()
{
static long lastMillis = 0;
long currentMillis = millis();
long elapsed = currentMillis - lastMillis;
if (elapsed > amberPeriod) {
if (lightsOn)
turnLightsOff();
else
turnLightsOn();
lastMillis = currentMillis;
}
}
void turnLightsOn()
{
tone(_buzzer, beepInFrequency, beepDuration);
digitalWrite(_left, HIGH);
digitalWrite(_right, HIGH);
lightsOn = true;
}
void turnLightsOff()
{
tone(_buzzer, beepOutFrequency, beepDuration);
digitalWrite(_left, LOW);
digitalWrite(_right, LOW);
lightsOn = false;
}
};