คลาสและวัตถุ: ฉันต้องใช้ไฟล์ประเภทใดและจำนวนเท่าใด


20

ฉันไม่เคยมีประสบการณ์กับ C ++ หรือ C มาก่อน แต่รู้วิธีเขียนโปรแกรม C # และเรียนรู้ Arduino ฉันต้องการจัดระเบียบภาพร่างของฉันและค่อนข้างสบายใจกับภาษา Arduino แม้จะมีข้อ จำกัด แต่ฉันอยากจะมีวิธีการเชิงวัตถุในการเขียนโปรแกรม Arduino ของฉัน

ดังนั้นฉันเห็นว่าคุณสามารถมีวิธีต่อไปนี้ (ไม่ใช่รายการครบถ้วนสมบูรณ์) เพื่อจัดระเบียบรหัส:

  1. ไฟล์. ini ไฟล์เดียว;
  2. ไฟล์. in หลายไฟล์ในโฟลเดอร์เดียวกัน (ที่ IDE เรียกและแสดงเช่น "แท็บ")
  3. ไฟล์. io ที่มีไฟล์. h และ. cpp ที่รวมอยู่ในโฟลเดอร์เดียวกัน
  4. เหมือนข้างบน แต่ไฟล์เป็นไลบรารี่ที่ติดตั้งไว้ในโฟลเดอร์โปรแกรม Arduino

ฉันยังได้ยินวิธีต่อไปนี้ แต่ยังไม่ได้ทำงาน:

  • ประกาศคลาส C ++ ในไฟล์. ini ไฟล์เดียว (เคยได้ยิน แต่ไม่เคยเห็นการทำงาน - เป็นไปได้ไหม?);
  • [วิธีการที่ต้องการ]รวมไฟล์. cpp ที่มีการประกาศคลาส แต่ไม่ต้องใช้ไฟล์. h (ต้องการวิธีนี้ควรใช้งานได้หรือไม่);

โปรดทราบว่าฉันต้องการใช้คลาสเท่านั้นเพื่อให้มีการแบ่งพาร์ติชันได้มากขึ้นแอปพลิเคชันของฉันควรเรียบง่ายมากโดยเฉพาะปุ่มที่เกี่ยวข้องกับไฟ LED และ buzzers ส่วนใหญ่


สำหรับผู้ที่สนใจมีการอภิปรายที่น่าสนใจเกี่ยวกับคำจำกัดความของคลาส headerless (cpp เท่านั้น) ที่นี่: programmers.stackexchange.com/a/35391/35959
heltonbiker

คำตอบ:


31

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เป็นต้น)

ผลข้างเคียงของสิ่งนี้คือคุณสามารถพิจารณาภาพร่างหลัก (ไฟล์. io) ให้เป็น 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 ของคุณด้วยชื่อเดียวกับไฟล์. io ของคุณ เนื่องจากไฟล์. ini กลายเป็นไฟล์. cpp ได้อย่างมีประสิทธิภาพสิ่งนี้จะทำให้คุณมีการปะทะกันของชื่อ


ประกาศคลาส C ++ ในไฟล์. ini ไฟล์เดียว (เคยได้ยิน แต่ไม่เคยเห็นการทำงาน - เป็นไปได้ไหม?);

ใช่คอมไพล์นี้ตกลง:

class foo {
  public:
};

foo bar;

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

อย่างไรก็ตามคุณอาจจะดีที่สุดที่จะปฏิบัติตามปกติ: ใส่การประกาศของคุณใน.hไฟล์และคำจำกัดความของคุณ (การใช้งาน) ใน.cpp(หรือ.c) ไฟล์

ทำไม "อาจจะ"

ตัวอย่างของฉันแสดงให้เห็นว่าคุณสามารถรวบรวมทุกอย่างไว้ในไฟล์เดียว สำหรับโครงการขนาดใหญ่จะดีกว่าที่จะจัดระเบียบมากขึ้น ในที่สุดคุณก็ขึ้นสู่เวทีในโครงการขนาดกลางถึงขนาดใหญ่ที่คุณต้องการแยกสิ่งต่าง ๆ ออกเป็น "กล่องดำ" - นั่นคือคลาสที่ทำสิ่งใดสิ่งหนึ่งทำได้ดีผ่านการทดสอบและอยู่ในตัวเอง ( เท่าที่จะทำได้)

หากคลาสนี้ถูกใช้ในไฟล์อื่นหลายไฟล์ในโครงการของคุณนี่คือที่ที่แยก.hและ.cppไฟล์เข้ามาเล่น

  • .hไฟล์ประกาศคลาส - นั่นคือจะให้รายละเอียดเพียงพอสำหรับไฟล์อื่น ๆ ที่จะรู้ว่าสิ่งที่มันไม่สิ่งที่ฟังก์ชั่นมันมีและวิธีการที่พวกเขาเรียกว่า

  • .cppไฟล์กำหนด (การดำเนินการ) ชั้น - นั่นคือจริง ๆ แล้วมันยังมีฟังก์ชั่นและสมาชิกระดับคงที่ที่ทำให้ชั้นเรียนทำสิ่งที่ตน เนื่องจากคุณต้องการนำไปใช้เพียงครั้งเดียวนี่เป็นไฟล์แยกต่างหาก

  • .hไฟล์คือสิ่งที่ได้รับการรวมอยู่ในไฟล์อื่น ๆ .cppไฟล์จะรวบรวมครั้งโดย IDE ที่จะใช้ฟังก์ชั่นการเรียน

ห้องสมุด

หากคุณทำตามกระบวนทัศน์นี้คุณก็พร้อมที่จะย้ายชั้นเรียนทั้งหมด ( .hและ.cppไฟล์) ไปยังไลบรารีได้อย่างง่ายดายมาก จากนั้นสามารถแชร์ระหว่างหลายโครงการ ทั้งหมดที่จำเป็นคือการทำให้โฟลเดอร์ (เช่น. myLibrary) และใส่.hและ.cppไฟล์ลง (เช่น. myLibrary.hและmyLibrary.cpp) แล้วใส่โฟลเดอร์นี้ภายในของคุณlibrariesโฟลเดอร์ในโฟลเดอร์ที่สเก็ตช์ของคุณจะถูกเก็บไว้ (โฟลเดอร์สมุด) ที่

รีสตาร์ท IDE และตอนนี้รู้เกี่ยวกับไลบรารีนี้แล้ว มันง่ายมาก ๆ และตอนนี้คุณสามารถแชร์ไลบรารีนี้กับหลาย ๆ โครงการได้ ฉันทำสิ่งนี้มาก


รายละเอียดอีกเล็กน้อยที่นี่


คำตอบที่ดี แม้ว่าหนึ่งในหัวข้อที่สำคัญที่สุดยังไม่เป็นที่ชัดเจนสำหรับฉัน: ทำไมทุกคนพูดว่า "คุณน่าจะดีที่สุดที่จะปฏิบัติตามปกติ: .h + .cpp" ทำไมจะดีกว่า ทำไมส่วนหนึ่งอาจ ? และที่สำคัญที่สุด: ฉันจะทำไม่ได้นั่นคือมีทั้งส่วนต่อประสานและการใช้งาน (นั่นคือรหัสชั้นเรียนทั้งหมด) ในไฟล์. cpp เดียวแบบเดียวกัน ขอบคุณมากตอนนี้! : o)
heltonbiker

เพิ่มย่อหน้าอีกสองสามย่อหน้าเพื่อตอบว่าทำไม "น่าจะ" คุณควรมีไฟล์แยกกัน
Nick Gammon

1
คุณไม่ทำมันได้อย่างไร เพียงแค่รวบรวมพวกเขาทั้งหมดไว้ด้วยกันตามที่แสดงในคำตอบของฉันอย่างไรก็ตามคุณอาจพบว่าตัวประมวลผลล่วงหน้าทำงานกับคุณ ข้อกำหนดคลาส C ++ ที่ถูกต้องสมบูรณ์บางตัวจะล้มเหลวหากใส่ไว้ในไฟล์. io หลัก
Nick Gammon

นอกจากนี้ยังจะล้มเหลวหากคุณรวมไฟล์. H ไว้ในไฟล์. cpp สองไฟล์และไฟล์. h มีรหัสซึ่งเป็นนิสัยที่พบได้ทั่วไป เปิดแหล่งที่มาเพียงแก้ไขด้วยตัวคุณเอง หากคุณไม่สะดวกที่จะทำเช่นนั้นคุณอาจไม่ควรใช้โอเพ่นซอร์ส คำอธิบายที่สวยงาม @Nick Gammon ดีกว่าทุกอย่างที่ฉันเคยเห็น

@ Spiked3 ไม่ได้เป็นเรื่องของการเลือกสิ่งที่ฉันสะดวกสบายที่สุดสำหรับตอนนี้มันเป็นเรื่องของการรู้ว่าสิ่งที่ฉันสามารถเลือกได้ตั้งแต่แรก ฉันจะเลือกได้อย่างถูกต้องได้อย่างไรถ้าฉันไม่รู้ด้วยซ้ำว่าตัวเลือกของฉันคืออะไรและทำไมแต่ละตัวเลือกถึงเป็นแบบนั้น อย่างที่ฉันบอกว่าฉันไม่เคยมีประสบการณ์กับ C ++ มาก่อนและดูเหมือนว่า C ++ ใน Arduino อาจต้องใช้ความระมัดระวังเป็นพิเศษดังที่แสดงในคำตอบที่ดีมากนี้ แต่ในที่สุดฉันก็แน่ใจว่าฉันเข้าใจแล้วทำสิ่งต่าง ๆ โดยไม่ต้องพลิกพวงมาลัยใหม่ (อย่างน้อยก็หวังว่าจะเป็นอย่างนั้น) :)
heltonbiker

6

คำแนะนำของฉันคือทำตามวิธีการทั่วไปของ C ++: แยกส่วนต่อประสานและการนำไปใช้กับไฟล์. h และ. cpp สำหรับแต่ละคลาส

มีจับไม่กี่:

  • คุณต้องการไฟล์. io อย่างน้อยหนึ่งไฟล์ - ฉันใช้ symlink กับไฟล์. cpp ที่ฉันสร้างอินสแตนซ์ของคลาส
  • คุณต้องให้การเรียกกลับที่สภาพแวดล้อม Arduino คาดหวัง (setu, loop, ฯลฯ )
  • ในบางกรณีคุณจะประหลาดใจกับสิ่งแปลกประหลาดที่ไม่ได้มาตรฐานซึ่งแตกต่างจาก Arduino IDE จากแบบปกติเช่นการรวมอัตโนมัติของไลบรารีบางอย่าง แต่ไม่ใช่แบบอื่น

หรือคุณอาจคลอง Arduino IDE และลองกับคราส ดังที่ฉันได้กล่าวถึงบางสิ่งที่ควรจะช่วยให้ผู้เริ่มต้นมีแนวโน้มที่จะเข้ามามีส่วนร่วมในการพัฒนามากขึ้น


ในขณะที่ฉันรู้สึกว่าการแยกร่างเป็นไฟล์หลาย ๆ ไฟล์ (แท็บหรือรวม) ช่วยให้ทุกอย่างอยู่ในสถานที่ของตัวเองฉันรู้สึกว่าต้องมีสองไฟล์ที่จะดูแลสิ่งเดียวกัน (.h และ. cpp) ความซ้ำซ้อน / การทำซ้ำที่ไม่จำเป็น รู้สึกเหมือนว่ามีการกำหนดชั้นเรียนสองครั้งและทุกครั้งที่ฉันต้องการเปลี่ยนสถานที่แห่งหนึ่งฉันต้องเปลี่ยนอีกแห่ง หมายเหตุสิ่งนี้ใช้กับกรณีง่าย ๆ เช่นของฉันซึ่งจะมีการนำไปใช้เพียงส่วนหัวเดียวเท่านั้นและจะใช้เพียงครั้งเดียว (ในภาพร่างเดียว)
heltonbiker

มันทำให้การทำงานของคอมไพเลอร์ / ลิงเกอร์ง่ายขึ้นและช่วยให้คุณมีองค์ประกอบไฟล์. cpp ซึ่งไม่ได้เป็นส่วนหนึ่งของคลาส แต่ใช้ในวิธีการบางอย่าง และในกรณีที่คลาสมี static memers คุณไม่สามารถวางไว้ในไฟล์. h
Igor Stoppa

การแยกไฟล์. h และ. cpp นั้นรู้จักกันมานานแล้วว่าไม่จำเป็น Java, C #, JS none ต้องการไฟล์ส่วนหัวและแม้กระทั่งมาตรฐาน cpp iso ก็พยายามที่จะย้ายออกไปจากพวกเขา ปัญหาคือมีรหัสดั้งเดิมมากเกินไปที่สามารถทำลายด้วยการเปลี่ยนแปลงที่รุนแรง นั่นคือเหตุผลที่เรามี CPP หลังจาก C และไม่ใช่เพียงซีซีแบบขยายฉันคาดหวังว่าจะเกิดขึ้นอีกครั้งเหมือนกันคือ CPX หลังจาก CPP?

แน่นอนถ้าการแก้ไขครั้งต่อไปเกิดขึ้นกับวิธีการทำงานที่ทำโดยส่วนหัวโดยไม่มีส่วนหัว ... แต่ในขณะเดียวกันก็มีหลายสิ่งที่ไม่สามารถทำได้โดยไม่มีส่วนหัว: ฉันต้องการดูว่าการรวบรวมแบบกระจาย สามารถเกิดขึ้นได้โดยไม่มีส่วนหัวและไม่มีค่าโสหุ้ยที่สำคัญ
Igor Stoppa

6

ฉันโพสต์คำตอบเพื่อความสมบูรณ์หลังจากค้นหาและทดสอบวิธีการประกาศและนำคลาสไปใช้ในไฟล์. cpp เดียวกันโดยไม่ต้องใช้ส่วนหัว ดังนั้นเกี่ยวกับการใช้ถ้อยคำที่แน่นอนของคำถามของฉัน "ฉันต้องใช้ประเภทไฟล์กี่คลาส" คำตอบปัจจุบันใช้สองไฟล์: หนึ่งไฟล์. iino ที่มีการตั้งค่าและลูปและ. cpp มีทั้งหมด (ค่อนข้างเรียบง่าย ) class หมายถึงสัญญาณเลี้ยวของรถของเล่น

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;
    }
};

1
นี่คือ java-like และตบการนำวิธีการไปใช้ในการประกาศคลาส นอกเหนือจากความสามารถในการอ่านที่ลดลง - ส่วนหัวช่วยให้คุณประกาศวิธีการในรูปแบบที่กระชับ - ฉันสงสัยว่าการประกาศในชั้นเรียนที่ผิดปกติมากขึ้น (เช่นสถิตยศาสตร์เพื่อน ฯลฯ ) จะยังคงใช้งานได้ แต่ตัวอย่างนี้ส่วนใหญ่ไม่ค่อยดีนักเพราะมันรวมไฟล์ไว้เพียงครั้งเดียวที่มีการรวมเข้าด้วยกัน ปัญหาที่แท้จริงเริ่มต้นเมื่อคุณรวมไฟล์เดียวกันในหลาย ๆ ที่และคุณเริ่มได้รับการประกาศวัตถุที่ขัดแย้งจาก linker
Igor Stoppa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.