레이블이 클래스인 게시물을 표시합니다. 모든 게시물 표시
레이블이 클래스인 게시물을 표시합니다. 모든 게시물 표시

2015년 6월 12일 금요일

C++ 클래스의 정적(static) 멤버

 클래스의 모든 멤버 변수와 멤버 함수는 정적(static) 멤버로 지정될 수 있다. 정적 멤버 변수는 인스턴스가 생성될 때마다 독립적으로 생기는 멤버 변수와 달리 해당 클래스에 하나만 생성되고 모든 인스턴스에서 공동으로 접근할 수 있는 변수이다. 마찬가지로 정적 멤버 함수도 클래스당 하나만 생기며 모든 인스턴스에서 공동으로 호출할 수 있다.

 멤버를 정적 멤버로 선언하려면 선언문 앞에 static 이라는 지정자를 붙이면 된다. 모든 멤버들이 static으로 선언될 수 있으며 정적 멤버들도 접근 지정자 (public, protected, private)을 붙일 수 있다.

1 정적 멤버 변수

 이전 포스트의 LED 클래스의 예를 가지고 정적 멤버 변수를 추가시켜 보면 다음과 같다.
class Led {
   public:
       Led();
       void turnOn();
       void turnOff();
       static int iCount; // 정적 멤버 변수
   private:
       byte _pin;
};
이 예제에서 iCount는 정적 멤버 변수로 선언되었다. 정적 멤버 변수를 초기화하기 위해서는 클래스 내부에서는 불가능하다. 다음과 같이 초기화 하려고 하면 에러를 발생한다.

class Led {
   public:
       Led();
       void turnOn();
       void turnOff();
       static int iCount = 0; // 오류 발생
   private:
       byte _pin;
};
 
따라서 클래스 외부에서 전역 변수처럼 초기화시켜야 한다.
int Led::iCount = 0;
정적 변수를 접근하는 방법은 두 가지가 있는데 먼저 클래스 이름으로 접근하는 것이다. 이때 범위 지정 연산자인 ::을 이용한다.

Led::iCount ++; // iCount값을 하나 증가시킨다.
 
또는 인스턴스를 이용해서도 접근할 수 있다.
Led led1(12), led2(11);
led1.iCount = 10;
led2.iCount = 30;
비록 인스턴스 멤버 변수를 접근하는 문법과 똑같지만 서로 다른 인스턴스를 통해서 정적 변수를 참조했다고 할지라도 결국 같은 하나의 정적 변수 iCount를 접근하는 것이다.
 정적 변수의 첫 번째 활용 목적은 인스턴스들 사이에서 공유할 변수를 만들고자 하는 것이다. 예를 들어서 인스턴스가 몇 개나 생성되었는지를 기록하고자 할 때 앞의 예에서 iCount를 사용하면 될 것이다. 이 경우 생성자 내에서 iCount 값을 증가시키면 새로운 인스턴스가 생성될 때마다 이 변수값이 증가되므로 이것으로 개수를 알 수 있다.
int Led::iCount = 0; // 전역에서 정적 멤버 변수 초기화
Led::Led(int pin) { //생성자
   _pin = pin;
   pinMode(_pin, OUTPUT);
   iCount++; // 인스턴스 생성 시 하나 증가
}
Led::~Led(int pin) { //소멸자
   iCount--; // 인스턴스 소멸 시 하나 감소
}
또한 전역 변수를 쓰는 대신 관련된 클래스 내부에 정적 멤버 변수를 집어넣어서 C언어의 전역 변수처럼 사용할 수도 있다.

2 정적 멤버 함수

 멤버 함수 앞에 static 이라는 키워드를 붙이면 정적 멤버 함수가 된다. 정적 변수와 마찬가지로 정적 멤버 함수는 인스턴스에 속하는 멤버 함수가 아니라 클래스에 속하는 하나의 함수이다. 정적 함수 내부에서는 오직 정적 변수만을 사용할 수 있으며 함수도 정적 함수만을 호출할 수 있다. 반대로 일반 멤버 함수에서는 정적 멤버를 접근하는데 전혀 제약이 없다. 다음 예를 보자.
class Util {
   public:
       int iA;
       static double dA;

       void getA() {
           iA += (int)dA; // 정적과 비정적 변수를 모두 사용가능
           return iA;
       }

       static void getB() { // 정적 함수
           return dA; //정적 변수만 사용 가능
       }
};

비정적 함수 getA() 내부에서는 변수 iA와 정적 변수 dA를 사용하는데 아무런 제약이 없으나 정적함수 getB() 에서는 정적 변수 dA만 사용할 수 있다. 마찬가지로 정적 함수 내에서는 정적인 함수만 호출할 수 있으며 일반적인 멤버 함수는 호출할 수 없다.


C++ 인라인(inline) 멤버 함수

 C++ 에서 함수를 호출하는데 있어서는 어느 정도 댓가가 따른다. 함수를 호출하기 직전에 기존 정보를 저장하고 인자를 넘겨주고 실행 순서를 바꾼 후 함수의 수행이 끝나면 사용한 메모리를 정리하고 반환값을 받는다. 즉 함수 호출 전후에 필수적으로 수행해야하고 메모리와 시간을 요하는 작업들이 있는 것이다. PC에서 수행되는 프로그램을 작성할 때에는 이러한 오버헤드는 무시할 수 있을 것이다. 하지만 아두이노와 같은 메모리나 성능이 매우 제약된 프로세서를 프로그래밍할 경우에는 무시 못할 성능 저하 요인이 될 수도 있다.

 C++의 인라인(inline)함수는 함수의 길이가 매우 짧을 경우에 효율을 높이기 위해서 도입된 방법으로서 함수의 선언부 앞에 inline 이라는 키워드로 구현된다. 이전의  코드로 예를 들면 다음과 같다.
inline boolean Button::isPushed() {
   return (digitalRead(_pin) == LOW)? true:false;
}

컴파일러는 인라인 함수를 호출하는 곳에 인라인 함수의 코드를 그대로 삽입하여 함수의 호출이 일어나지 않도록 한다. 이렇게 되면 함수를 호출할 때 드는 비용이 없어지기 때문에 속도가 더 올라간다. 반대 급부로 프로그램의 길이는 더 길어지게 된다.
 다른 객체지향 언어(JAVA, C# 등등)도 마찬가지지만 C++도 비교적 작은 크기의 함수를 많이 사용한다. 대표적인 것이 멤버 변수를 접근하는 getter/setter 함수인데 이러한 사용 빈도가 높고 크기가 작은 함수들을 inline으로 정의해 놓으면 더 높은 실행 효율을 얻을 수 있다.

 함수를 inline으로 설정했다고 모두 코드를 치환하는 것은 아니고 컴파일러에 따라서 동작 방식이 다르다. 자기 자신을 호출하는 재귀함수나 반복문, switch문 등이 포함된 함수는 inline으로 지정했다고 하더라도 컴파일러에 의해서 인라인 선언이 무시될 수도 있다. 또한 생성자를 포함하여 모든 멤버 함수가 인라인으로 선언될 수 있다. 이전 포스트의 Button클래스의 멤버 함수를 인라인 함수로 지정하면 다음과 같다.
class Button {
   public:
     Button(byte, boolean); // 생성자
     boolean isPushed(); // 버튼이 눌렸다면 true를 반환하는 멤버 함수
   private:
     byte _pin; // 연결된 핀
     boolean _internalPullup; //내부에 풀업이 되었는가
};
// 생성자의 구현
inline Button::Button(byte pin, boolean internalPullUp = true) {
   _pin = pin;
   _internalPullup = internalPullUp;
   if (_internalPullup)
       pinMode(_pin, INPUT_PULLUP);
   else
   pinMode(_pin, INPUT);
}
// 멤버 함수의 구현
inline boolean Button::isPushed() {
   return (digitalRead(_pin) == LOW)? true:false;
}
 멤버 함수의 크기가 작을 경우 클래스의 선언부에 바로 함수를 구현해도 무방하다. 즉, 위 예의 isPushed() 함수를 다음과 같이 클래스의 선언부에 바로 구현해도 된다.
class Button {
   public:
     Button(byte, boolean); // 생성자
     boolean isPushed();{
       return (digitalRead(_pin) == LOW)? true:false;
     }
     boolean isPushed(); // 버튼이 눌렸다면 true를 반환하는 멤버 함수
   private:
     byte _pin; // 연결된 핀
     boolean _internalPullup; //내부에 풀업이 되었는가
};
// 생성자의 구현
inline Button::Button(byte pin, boolean internalPullUp = true) {
   _pin = pin;
   _internalPullup = internalPullUp;
   if (_internalPullup)
       pinMode(_pin, INPUT_PULLUP);
   else
       pinMode(_pin, INPUT);
}
 단 한 가지 주의할 점은 컴파일러는 클래스 선언부에 바로 구현된 멤버 함수들에 대해서는 inline선언이 없어도 인라인 함수로 자동으로 처리한다. 따라서 이 예에서 isPushed()함수는 인라인 함수로 자동으로 간주된다.


C++로 작성한 Button 클래스 (아두이노) 예제

 여기에서는 입력 장치로 많이 사용되는 푸시 버튼을 객체화 시키는 예제를 작성해 보도록 하겠다. 푸시버튼도 디지털 핀에 연결할 수 있으며 외부에서 풀업(pull-up)을 시켜주느냐 아니면 내부 풀업을 연결하느냐를 지정할 수 있도록 하겠다. Led 클래스와 마찬가지로 연결된 핀 번호는 private 변수에 저장한다. 그리고 내부에 풀업이 되었는지의 여부를 저장하는 변수도 private변수에 저장한다.

 클래스의 선언을 다음과 같다.
class Button {
public:
   Button(byte, boolean); // 생성자
   boolean isPushed(); // 버튼이 눌렸다면 true를 반환하는 멤버 함수
private:
   byte _pin; // 연결된 핀
   boolean _internalPullup; //내부에 풀업이 되었는가
};
생성자를 보면 입력 인수의 타입만 지정해 주었음을 알 수 있다. 생성자와 멤버 함수의 구현부는 다음과 같다.
// 생성자의 구현
Button::Button(byte pin, boolean internalPullUp = true) {
   _pin = pin;
   _internalPullup = internalPullUp;
   if (_internalPullup)
       pinMode(_pin, INPUT_PULLUP);
   else
       pinMode(_pin, INPUT);
}
// 멤버 함수의 구현
boolean Button::isPushed() {
   return (digitalRead(_pin) == LOW)? true:false;
}
생성자의 구현을 보면 다음과 같이 기본값 인자가 사용되었다.

Button::Button(byte pin, boolean internalPullUp = true) {...}
이렇게 구현해 놓으면 생성자는 인자를 하나를 받을 수도 있고 두 개를 받을 수도 있는데 하나만 있다면 두 번째는 자동으로 true로 지정된다. 즉, 다음과 같이 두 가지로 생성하는 것이 가능하다.
Button btn1(12); // 내부 풀업 저항을 연결하는 경우
Button btn2(11, false); // 외부에서 풀업 된 경우
두 번째 인자의 값에 따라서 pinMode()함수를 적절하게 호출하였음을 알 수 있다.

 풀업 저항이 연결되었을 경우 푸시버튼을 누른 상태에서 digitalRead()함수의 반환값은 LOW가 된다. 따라서 이에 맞게 멤버 함수 isPushed() 가 작성되었음을 알 수 있다.
 만약 클래스 선언이 Button.h에, 구현부가 Button.cpp 에 저장되어 라이브러리를 구성했다면, 이것을 이용하여 버튼이 눌리면 내장 LED가 켜지고 그렇지 않으면 꺼지는 프로그램을 다음과 같이 작성할 수 있다.
#include <Button.h> // 라이브러리 인클루드
Button btn1(12); //12번 핀에 내부 풀업 연결
void setup() {
   pinMode(13, OUTPUT);
}
void loop() {
   if (btn1.isPushed()) // 만약 버튼이 눌렸다면
       digitalWrite(13, HIGH);
   else // 그렇지 않다면 (눌리지 않았다면)
       digitalWrite(13, LOW);
}
 
이와 같이 class를 이용하여 객체화를 시도하면 프로그램을 좀 더 직관적으로 작성할 수 있다.


2015년 5월 30일 토요일

파이썬 클래스의 __init__() 메쏘드와 self 인자

  이전 포스트에서 객체를 생성하는 것이 외형상으로 함수를 호출하는 것과 유사하다고 했는데, 사실 객체를 생성할 때 클래스의 __init__() 메소드를 호출하게 된다. 이 메쏘드는 다음과 같이 사용자가 정의할 수 있다.

class 클래스명:
def __init__(self):
메소드 본체

이 예에서 __init__(self) 메쏘드는 객체를 생성할 때 자동으로 호출되는 특수한 메소드이고 반드시 첫 번째 인자는 self 이어야 한다. 이 메쏘드 내부에 클래스 변수를 생성할 수 있다.

class Robot:
   def __init__(self):
       self.nLegs = 2
       self.nArms = 2

이제

>>> asimo = Robot()

이라고 Robot객체를 생성하면 nLegs 와 nArms 필드가 생성되고 초기화 되었음을 알 수 있다.

>>> asimo.nLegs
2
>>> asimo.nArms
2

객체의 필드를 확인하고 싶다면 내장 함수 vars()를 이용하면 된다.

>>> vars(asimo)
{'nLegs': 2, 'nArms': 2}

이 내장 함수는 사실 객체의 __dict__ 내부 필드를 반환한다. 이 내부 필드에 객체의 필드들이 딕셔너리의 요소로 저장되어 있다.

객체를 생성할 때 이름을 사용자가 입력하도록 하려면 다음과 같이 __init__() 메소드에 일반 인자를 self 뒤에 주면 된다.

class Robot:
   def __init__(self, name):
       self.nLegs = 2
       self.nArms = 2
       self.name = name

이제 객체를 생성할 때 이름을 반드시 입력해야 한다.

>>> asimo=Robot('asimo')
>>> asimo.name
'asimo'

만약 이름이 주어지지 않았을 때 ‘dummy’ 라는 이름을 주고 싶다면 다음과 같이 기본값 인자를 사용하면 된다.

class Robot:
   def __init__(self, name=’dummy’):
       self.nLegs = 2
       self.nArms = 2
       self.name = name
    
이제 객체를 생성할 때 이름을 입력하지 않아도 된다.

>>> asimo=Robot()
>>> asimo.name
'dummy'

이와 같이 __init__() 생성자 함수도 파이썬의 일반 함수처럼 가변 개수 인자, 기본값 인자, 키워드 인자 등을 적용할 수 있다.