클래스와 객체 : 실제로 얼마나 많은 파일 형식을 사용해야합니까?


20

C ++ 또는 C에 대한 이전 경험이 없지만 C #을 프로그래밍하는 방법을 알고 Arduino를 배우고 있습니다. 스케치를 정리하고 제한 사항이 있어도 Arduino 언어에 익숙하지만 Arduino 프로그래밍에 객체 지향 접근 방식을 원합니다.

그래서 코드를 구성하는 다음과 같은 방법 (전체 목록이 아님)을 가질 수 있음을 알았습니다.

  1. 단일 .ino 파일;
  2. 동일한 폴더에있는 여러 개의 .ino 파일 (IDE가 호출하고 "탭"과 같이 표시)
  3. 같은 폴더에 .h 및 .cpp 파일이 포함 된 .ino 파일
  4. 위와 동일하지만 파일은 Arduino 프로그램 폴더 안에 설치된 라이브러리입니다.

나는 또한 다음과 같은 방법에 대해 들었지만 아직 작동하지 않았습니다.

  • 동일한 단일 .ino 파일에서 C ++ 스타일 클래스를 선언합니다 (들어 봤지만 작동하지는 않았지만 가능합니까?);
  • [선호되는 접근법] 클래스가 선언되었지만 .h 파일 사용 하지 않는 .cpp 파일 포함 (이 접근 방식과 같은 방식으로 작동합니까?);

코드를 더 분할하기 위해 클래스 만 사용하고 싶습니다. 응용 프로그램은 매우 간단해야하며 대부분 버튼, LED 및 버저 만 포함해야합니다.


관심있는 사람들을 위해 여기에 헤더가없는 (cpp 만) 클래스 정의에 대한 흥미로운 토론이 있습니다 : programmers.stackexchange.com/a/35391/35959
heltonbiker

답변:


31

IDE가 물건을 구성하는 방법

첫 번째는 IDE가 "스케치"를 구성하는 방법입니다.

  • .ino. 파일은가, 그래서에있는 폴더와 동일한 이름의 하나 foobar.inofoobar폴더 - 주요 파일이 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가 포함되어 있으므로 그렇게하면 복제본이 생깁니다.

  • 기본 .ino 파일과 동일한 이름으로 .cpp 파일의 이름을 지정하지 마십시오. .ino 파일이 효과적으로 .cpp 파일이되므로 이름 충돌이 발생합니다.


동일한 단일 .ino 파일에서 C ++ 스타일 클래스를 선언합니다 (들어 봤지만 작동하지는 않았지만 가능합니까?);

예, 다음과 같이 컴파일됩니다.

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 파일에서 구현 (인공 고관절이며, 전체 클래스 코드를)? 지금 대단히 감사합니다! : o)
heltonbiker

"아마도"별도의 파일을 가져야하는 이유에 대한 답변을 위해 두 개의 단락을 추가했습니다.
Nick Gammon

1
당신은 어떻게합니까 하지 그것을? 내 대답에 나와있는 것처럼 모두 함께 넣으십시오. 그러나 전처리 기가 작동하지 않을 수 있습니다. 완벽하게 유효한 일부 C ++ 클래스 정의가 기본 .ino 파일에 있으면 실패합니다.
Nick Gammon

또한 두 개의 .cpp 파일에 .H 파일을 포함시키고 해당 .h 파일에 코드가 포함되어 있으면 일부는 일반적인 습관입니다. 오픈 소스이므로 직접 수정하십시오. 편안하지 않다면 아마도 오픈 소스를 사용해서는 안됩니다. @Nick Gammon의 아름다운 설명, 지금까지 내가 본 것보다 낫습니다.

@ Spiked3 내가 가장 편한 것을 고르는 문제는 지금 당장 내가 선택할 수있는 것이 무엇인지 아는 문제이다. 내 옵션이 무엇인지 모르고 각 옵션이 왜 그런지 잘 모르겠다면 어떻게 합리적인 선택을 할 수 있습니까? 내가 말했듯이, 나는 C ++에 대한 이전 경험이 없으며 Arduino의 C ++ 은이 답변에 표시된 것처럼 추가주의가 필요할 수 있습니다. 그러나 나는 확실히 결국 나는 그것의 이해를 얻고있어 내 물건 (적어도 난 그렇게 희망) : 바퀴를 개혁하지 않고 완수
heltonbiker을

6

내 조언은 일반적인 C ++ 방식의 작업을 수행하는 것입니다 : 인터페이스와 구현을 각 클래스의 .h 및 .cpp 파일로 분리하십시오.

몇 가지주의 사항이 있습니다.

  • 적어도 하나의 .ino 파일이 필요합니다. 클래스를 인스턴스화하는 .cpp 파일에 대한 심볼릭 링크를 사용합니다.
  • Arduino 환경에서 예상되는 콜백 (setu, loop 등)을 제공해야합니다.
  • 어떤 경우에는 특정 라이브러리의 자동 포함과 같은 일반적인 라이브러리와 Arduino IDE를 차별화하는 비표준 이상한 것들에 놀랄 것입니다.

또는 Arduino IDE를 버리고 Eclipse를 사용해보십시오 . 앞서 언급했듯이 초보자에게 도움이되는 것들 중 일부는 숙련 된 개발자에게 방해가되는 경향이 있습니다.


스케치를 여러 파일 (탭 또는 포함)로 분리하면 모든 것이 제자리에있는 데 도움이된다고 생각하지만 동일한 것을 처리하기 위해 두 개의 파일이 필요하다고 생각합니다 (.h 및 .cpp)는 일종의 불필요한 중복 / 복제 클래스가 두 번 정의되는 것처럼 느껴지고 한 장소를 변경해야 할 때마다 다른 장소를 변경해야합니다. 이것은 주어진 헤더의 구현이 하나만 있고 단일 스케치에서 한 번만 사용되는 광산과 같은 간단한 경우에만 적용됩니다.
heltonbiker

컴파일러 / 링커의 작업을 단순화하고 클래스의 일부는 아니지만 일부 방법에 사용되는 .cpp 파일 요소를 가질 수 있습니다. 그리고 클래스에 정적 memer가있는 경우 .h 파일에 배치 할 수 없습니다.
Igor Stoppa

.h 및 .cpp 파일 분리는 오랫동안 필요하지 않은 것으로 인식되었습니다. Java, C #, JS는 헤더 파일이 필요 없으며 cpp iso 표준조차도 헤더 파일에서 멀어 지려고합니다. 문제는 그러한 급격한 변화로 깨질 수있는 레거시 코드가 너무 많다는 것입니다. 그것이 우리가 확장 된 C가 아니라 C 이후에 CPP를 갖는 이유입니다. CPP 이후에도 CPX가 다시 일어날 것으로 예상합니까?

물론, 다음 개정판에서 헤더없이 헤더로 수행되는 동일한 작업을 수행하는 방법이 나오면 헤더 없이는 할 수없는 일이 많이 있습니다. 분산 컴파일 방법을보고 싶습니다. 헤더와 주요 오버 헤드없이 발생할 수 있습니다.
Igor Stoppa

6

헤더를 사용하지 않고 동일한 .cpp 파일에서 클래스 를 선언 하고 구현하는 방법을 찾아 테스트 한 후 완성도에 대한 답변을 게시하고 있습니다. 따라서 내 질문 "클래스를 사용하는 데 필요한 파일 형식 수"의 정확한 문구와 관련하여 현재 답변에는 포함, 설정 및 루프가있는 .ino 파일과 전체를 포함하는 .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;
    }
};

1
이것은 자바와 유사하며 메소드 구현을 클래스 선언에 넣습니다. 가독성을 줄인 것 외에도 헤더는 간결한 형태로 메소드를 선언합니다. 더 이상한 클래스 선언 (정적, 친구 등)이 여전히 작동하는지 궁금합니다. 그러나이 예제의 대부분은 포함이 단순히 연결 되면 파일을 포함하기 때문에 실제로 좋지 않습니다 . 동일한 파일을 여러 위치에 포함시키고 링커에서 충돌하는 객체 선언을 시작하면 실제 문제가 시작됩니다.
Igor Stoppa
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.