Kelas dan objek: berapa banyak dan jenis file apa yang sebenarnya saya perlukan untuk menggunakannya?

20

Saya tidak punya pengalaman sebelumnya dengan C ++ atau C, tetapi tahu cara memprogram C # dan saya belajar Arduino. Saya hanya ingin mengatur sketsa saya dan cukup nyaman dengan bahasa Arduino bahkan dengan keterbatasannya, tetapi saya benar-benar ingin memiliki pendekatan berorientasi objek untuk pemrograman Arduino saya.

Jadi saya telah melihat bahwa Anda dapat memiliki cara berikut (bukan daftar lengkap) untuk mengatur kode:

  1. Satu file .ino;
  2. Beberapa file .ino dalam folder yang sama (apa yang IDE panggil dan tampilkan seperti "tab");
  3. File .ino dengan file .h dan .cpp yang disertakan dalam folder yang sama;
  4. Sama seperti di atas, tetapi file-file tersebut adalah pustaka yang diinstal di dalam folder program Arduino.

Saya juga telah mendengar cara-cara berikut, tetapi belum membuatnya bekerja:

  • Mendeklarasikan kelas gaya C ++ dalam file .ino yang sama, tunggal (pernah dengar, tetapi tidak pernah terlihat berfungsi - apakah itu mungkin?);
  • [pendekatan yang disukai] Termasuk file .cpp di mana kelas dideklarasikan, tetapi tanpa menggunakan file .h (ingin pendekatan ini, haruskah itu berhasil?);

Perhatikan bahwa saya hanya ingin menggunakan kelas sehingga kode lebih dipartisi, aplikasi saya harus sangat sederhana, hanya melibatkan tombol, led dan buzzers.

heltonbiker
sumber
Bagi mereka yang tertarik, ada diskusi menarik tentang definisi kelas tanpa header (hanya cpp) di sini: programmers.stackexchange.com/a/35391/35959
heltonbiker

Jawaban:

31

Bagaimana IDE mengatur berbagai hal

Hal pertama, ini adalah bagaimana IDE mengatur "sketsa" Anda:

  • File utama .inoadalah salah satu nama yang sama dengan folder di dalamnya. Jadi, untuk foobar.inodi foobarfolder - file utama adalah foobar.ino.
  • .inoFile - file lain di folder itu digabungkan bersama-sama, dalam urutan abjad, di akhir file utama (terlepas dari di mana file utama berada, berdasarkan abjad).
  • File gabungan ini menjadi .cppfile (mis. foobar.cpp) - ditempatkan di folder kompilasi sementara.
  • Preprosesor "membantu" menghasilkan prototipe fungsi untuk fungsi yang ditemukan dalam file itu.
  • File utama dipindai untuk #include <libraryname>arahan. Ini memicu IDE untuk juga menyalin semua file yang relevan dari masing-masing (disebutkan) perpustakaan ke folder sementara, dan menghasilkan instruksi untuk mengkompilasi mereka.
  • Apa pun .c, .cppatau .asmfile dalam folder sketsa ditambahkan ke proses build sebagai unit kompilasi terpisah (yaitu, mereka dikompilasi dengan cara biasa sebagai file terpisah)
  • Setiap .hfile juga disalin ke dalam folder kompilasi sementara, sehingga mereka dapat disebut dengan c atau Cpp file Anda.
  • Kompiler menambahkan ke file standar proses build (seperti main.cpp)
  • Proses build kemudian mengkompilasi semua file di atas menjadi file objek.
  • Jika fase kompilasi berhasil mereka dihubungkan bersama dengan pustaka standar AVR (mis. Memberi Anda strcpydll.)

Efek samping dari semua ini adalah Anda dapat menganggap sketsa utama (file .ino) sebagai C ++ untuk semua maksud dan tujuan. Namun, pembuatan prototipe fungsi dapat menyebabkan pesan kesalahan yang tidak jelas jika Anda tidak berhati-hati.


Menghindari kebiasaan pra-prosesor

Cara paling sederhana untuk menghindari keanehan ini adalah membiarkan sketsa utama Anda kosong (dan tidak menggunakan .inofile lain ). Kemudian buat tab lain ( .cppfile) dan masukkan barang-barang Anda ke dalamnya seperti ini:

#include <Arduino.h>

// put your sketch here ...

void setup ()
  {

  }  // end of setup

void loop ()
  {

  }  // end of loop

Perhatikan bahwa Anda perlu memasukkan Arduino.h. IDE melakukan itu secara otomatis untuk sketsa utama, tetapi untuk unit kompilasi lain, Anda harus melakukannya. Kalau tidak, ia tidak akan tahu tentang hal-hal seperti String, register perangkat keras, dll.


Menghindari pengaturan / paradigma utama

Anda tidak harus menjalankan dengan konsep setup / loop. Misalnya, file .cpp Anda dapat:

#include <Arduino.h>

int main ()
  {
  init ();  // initialize timers
  Serial.begin (115200);
  Serial.println ("Hello, world");
  Serial.flush (); // let serial printing finish
  }  // end of main

Paksakan inklusi perpustakaan

Jika Anda menjalankan dengan konsep "sketsa kosong" Anda masih perlu menyertakan perpustakaan yang digunakan di tempat lain dalam proyek, misalnya dalam .inofile utama Anda :

#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>

Ini karena IDE hanya memindai file utama untuk penggunaan perpustakaan. Secara efektif Anda dapat mempertimbangkan file utama sebagai file "proyek" yang menominasikan perpustakaan eksternal mana yang digunakan.


Masalah penamaan

  • Jangan beri nama sketsa utama Anda "main.cpp" - IDE menyertakan main.cpp sendiri sehingga Anda akan memiliki duplikat jika melakukannya.

  • Jangan beri nama file .cpp Anda dengan nama yang sama dengan file .ino utama Anda. Karena file .ino secara efektif menjadi file .cpp ini juga akan memberi Anda nama bentrokan.


Mendeklarasikan kelas gaya C ++ dalam file .ino yang sama, tunggal (pernah dengar, tetapi tidak pernah terlihat berfungsi - apakah itu mungkin?);

Ya, ini mengkompilasi OK:

class foo {
  public:
};

foo bar;

void setup () { }
void loop () { }

Namun Anda mungkin lebih baik mengikuti praktik normal: Masukkan deklarasi Anda dalam .hfile dan definisi Anda (implementasi) dalam file .cpp(atau .c).

Kenapa "mungkin"?

Seperti contoh saya menunjukkan Anda dapat meletakkan semuanya dalam satu file. Untuk proyek yang lebih besar lebih baik lebih terorganisir. Akhirnya Anda sampai ke tahap dalam proyek ukuran sedang hingga besar di mana Anda ingin memisahkan hal-hal menjadi "kotak hitam" - yaitu, kelas yang melakukan satu hal, melakukannya dengan baik, diuji, dan mandiri ( sejauh mungkin).

Jika kelas ini kemudian digunakan dalam banyak file lain di proyek Anda ini adalah di mana file terpisah .hdan .cppikut bermain.

  • The .hFile menyatakan kelas - yaitu, ia menyediakan cukup detail untuk file lain tahu apa yang dilakukannya, apa fungsi itu, dan bagaimana mereka disebut.

  • The .cppFile mendefinisikan (mengimplementasikan) kelas - yaitu, itu benar-benar menyediakan fungsi, dan anggota kelas statis, yang membuat kelas melakukan hal tersebut. Karena Anda hanya ingin mengimplementasikannya sekali, ini ada dalam file terpisah.

  • The .hfile apa yang akan dimasukkan ke dalam file lainnya. The .cppfile dikompilasi sekali oleh IDE untuk melaksanakan fungsi kelas.

Perpustakaan

Jika Anda mengikuti paradigma ini, maka Anda siap untuk memindahkan seluruh kelas ( .hdan .cppfile) ke perpustakaan dengan sangat mudah. Kemudian dapat dibagi antara beberapa proyek. Semua yang dibutuhkan adalah untuk membuat folder (misalnya. myLibrary) Dan menempatkan .hdan .cppfile ke dalamnya (misalnya. myLibrary.hDan myLibrary.cpp) dan kemudian menempatkan folder ini di dalam Anda librariesfolder dalam folder di mana sketsa Anda disimpan (folder sketsa).

Mulai ulang IDE dan sekarang tahu tentang perpustakaan ini. Ini sangat sederhana, dan sekarang Anda dapat berbagi perpustakaan ini melalui beberapa proyek. Saya sering melakukan ini.


Lebih detail di sini .

Nick Gammon
sumber
Jawaban bagus. Namun, satu topik paling penting belum menjadi jelas bagi saya: mengapa semua orang mengatakan "Anda mungkin lebih baik mengikuti praktik normal: .h + .cpp"? Kenapa lebih baik? Mengapa bagian mungkin ? Dan yang paling penting: bagaimana saya tidak bisa melakukannya, yaitu, memiliki antarmuka dan implementasi (yaitu, seluruh kode kelas) dalam file .cpp tunggal yang sama? Terima kasih banyak untuk saat ini! : o)
heltonbiker
Menambahkan beberapa paragraf lain untuk menjawab mengapa "mungkin" Anda harus memiliki file terpisah.
Nick Gammon
1
Bagaimana kamu tidak melakukannya? Masukkan saja semuanya seperti yang diilustrasikan dalam jawaban saya, namun Anda mungkin menemukan bahwa preprosesor bekerja melawan Anda. Beberapa definisi kelas C ++ yang valid sempurna gagal jika ditempatkan dalam file .ino utama.
Nick Gammon
Mereka juga akan gagal jika Anda memasukkan file .H dalam dua file .cpp Anda dan file .h itu berisi kode, yang merupakan kebiasaan umum beberapa orang. Sumbernya terbuka, perbaiki sendiri. Jika Anda tidak nyaman melakukan itu, Anda mungkin tidak boleh menggunakan open source. Penjelasan indah @Nick Gammon, lebih baik dari apapun yang saya lihat sejauh ini.
@ Spiked3 Bukan masalah memilih apa yang paling nyaman bagi saya, untuk saat ini, ini adalah masalah mengetahui apa yang tersedia bagi saya untuk dipilih. Bagaimana saya bisa membuat pilihan yang masuk akal jika saya bahkan tidak tahu apa pilihan saya, dan mengapa masing-masing pilihan seperti itu? Seperti yang saya katakan, saya tidak punya pengalaman sebelumnya dengan C ++, dan sepertinya C ++ di Arduino mungkin memerlukan perawatan ekstra, seperti yang ditunjukkan dalam jawaban ini. Tapi saya yakin pada akhirnya saya akan memahami hal itu dan menyelesaikan pekerjaan saya tanpa menciptakan kembali roda (setidaknya saya harap begitu) :)
heltonbiker
6

Saran saya adalah tetap berpegang pada cara C ++ khas dalam melakukan sesuatu: pisahkan antarmuka dan implementasi ke dalam file .h dan .cpp untuk setiap kelas.

Ada beberapa tangkapan:

  • Anda memerlukan setidaknya satu file .ino - Saya menggunakan symlink ke file .cpp di mana saya instantiate kelas.
  • Anda harus memberikan callback yang diharapkan oleh lingkungan Arduino (setu, loop, dll.)
  • dalam beberapa kasus Anda akan terkejut dengan hal-hal aneh yang tidak standar yang membedakan Arduino IDE dari yang normal, seperti masuknya otomatis perpustakaan tertentu, tetapi tidak yang lain.

Atau, Anda bisa membuang IDE Arduino dan mencoba dengan Eclipse . Seperti yang saya sebutkan, beberapa hal yang seharusnya membantu pemula, cenderung menghalangi pengembang yang lebih berpengalaman.

Igor Stoppa
sumber
Sementara saya merasa bahwa memisahkan sketsa menjadi beberapa file (tab atau termasuk) membantu semuanya berada di tempatnya sendiri, saya merasa perlu memiliki dua file untuk mengurus hal yang sama (.h dan .cpp) adalah semacam redundansi / duplikasi yang tidak perlu. Rasanya kelas didefinisikan dua kali, dan setiap kali saya perlu mengubah satu tempat, saya harus mengubah yang lain. Catatan ini hanya berlaku untuk kasus-kasus sederhana seperti milik saya, di mana hanya akan ada satu implementasi dari header yang diberikan, dan mereka akan digunakan hanya sekali (dalam sketsa tunggal).
heltonbiker
Ini menyederhanakan pekerjaan compiler / linker dan memungkinkan Anda untuk memiliki elemen file .cpp yang bukan bagian dari kelas, tetapi digunakan dalam beberapa metode. Dan jika kelas memiliki memer statis, Anda tidak dapat menempatkannya di file .h.
Igor Stoppa
Memisahkan file .h dan .cpp telah lama diakui sebagai tidak dibutuhkan. Java, C #, JS tidak memerlukan file header, dan bahkan standar iso cpp berusaha untuk menjauh darinya. Masalahnya adalah terlalu banyak kode warisan yang bisa dipecah dengan perubahan radikal. Itulah alasan kami memiliki CPP setelah C, dan bukan hanya C yang diperluas. Saya berharap hal yang sama akan terjadi lagi, CPX setelah CPP?
Tentu, jika revisi berikutnya datang dengan cara untuk melakukan tugas yang sama yang dilakukan oleh header, tanpa header ... tetapi sementara itu ada banyak hal yang tidak dapat dilakukan tanpa header: Saya ingin melihat bagaimana kompilasi didistribusikan dapat terjadi tanpa header dan tanpa mengeluarkan biaya besar.
Igor Stoppa
6

Saya memposting jawaban hanya untuk kelengkapan, setelah menemukan dan menguji cara mendeklarasikan dan mengimplementasikan kelas dalam file .cpp yang sama, tanpa menggunakan header. Jadi, mengenai frasa yang tepat dari pertanyaan saya, "berapa banyak tipe file yang perlu saya gunakan kelas", jawaban ini menggunakan dua file: satu .ino dengan menyertakan, pengaturan dan loop, dan .cpp yang berisi keseluruhan (agak minimalis) ) kelas, mewakili sinyal belok kendaraan mainan.

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;
    }
};
heltonbiker
sumber
1
Ini seperti java dan menampar implementasi metode ke dalam deklarasi kelas. Terlepas dari berkurangnya keterbacaan - header memberi Anda deklarasi metode dalam bentuk ringkas - Saya ingin tahu apakah deklarasi kelas yang lebih tidak biasa (seperti dengan statika, teman, dll) masih akan berfungsi. Tetapi sebagian besar contoh ini tidak terlalu bagus, karena hanya menyertakan file sekali penyertaan. Masalah sebenarnya dimulai ketika Anda memasukkan file yang sama di banyak tempat dan Anda mulai mendapatkan deklarasi objek yang bertentangan dari linker.
Igor Stoppa