내장형 하드웨어/C++

C++ 클래스, 접근속성, scope 연산자(::), inline

동화다아아 2011. 11. 10. 11:02

- 구조체 안에 함수를 사용할 수 있는 것의 발전되어 클래스가 되었다.
 → C++은 일반 구조체에서도 함수를 사용할 수 있다.(C에서는 불가능)

- 객체는 데이터 위주(예제의 OBJ를 함수로 둘러싼다.)
 → 구조적 프로그램은 자료와 함수가 분리되어 있고, 자료에 쉽게 접근하게 된다.(자료에 대한 보호가 부실)
 → 객체는 자료에 대해 일괄적인 인터페이스를 제공하고 사용용도를 유도할 수 있다.
 → 예제

#include<iostream.h>
#include<stdlib.h>


struct emb
{
  int *item;
  int iNum;  
  
  // 구조체 초기화 함수
  bool embInit(emb *stVal)
  {  
    // 분홍색 값은 리터럴(문자상수)이다.
    if(0 == stVal) // Null 을 입력받으면
    {
      return false;
    }
    stVal->item = 0;
    stVal->iNum = 0;
    cout << "embInit" << endl;

    return true;
  }
  // 구조체 메모리 할당
  bool embInit(emb *stVal, unsigned int uiNum)
  {
    // 분홍색 값은 리터럴(문자상수)이다.
    if(0 == stVal) // Null 을 입력받으면
    {
      return false;
    }
    else if(0 == uiNum)
    {
      return false;
    }
    // item에 동적 할당
    stVal->item = (int *)malloc(sizeof(int)*uiNum);
    // 동적 할당을 못 받았을 경우
    if(0 == stVal->item)
    {
      return false;
    }
    stVal->iNum = uiNum;
    cout << "embInit" << endl;
  
    return true;
  }
  // 구조체 메모리 초기화 함수
  void embFree(emb *stVal)
  {
    if(0 == stVal)
    {
      return ;
    }
    if(0 == stVal->item)
    {
      return ;
    }
    free(stVal->item);
    stVal->item = 0;
    stVal->iNum = 0;
    cout << "embFree" << endl;
    return ;
      
  }  
};
int main()
{
  emb OBJ;
  // 자료의 추상화
  // OBJ에 함수만이 접근하고 main()은 접근하지 않는다.
  // 즉, 함수를 통해서 간접적으로 건드리게 하는데
  // 이것을 OBJ가 추상화 되었다. 자료가 추상화 되었다고 한다.
  OBJ.embInit(&OBJ); // 0으로 초기화 하는 함수
  OBJ.embInit(&OBJ, 300); // 동적할당 및 전체 계수(1200byte)
  OBJ.embFree(&OBJ);
  
  return 0;
}

→ 실행 결과

 → 위와 같이 구조체에 함수를 추가하여도 잘 동작 하는 것을 확인할 수 있다.
 → 위에서 함수는 구조체 내에 있으므로 인자로 받을 필요 없이 구조체 내의 변수를 사용할 수 있으므로 인자를 제거한다.
 → 소스를 바꾸어 보면

#include<iostream.h>
#include<stdlib.h>


struct emb
{
  int *item;
  int iNum;  
  
  // 구조체 초기화 함수
  bool embInit()
  {  
    // 분홍색 값은 리터럴(문자상수)이다.
    item = 0;
    iNum = 0;
    cout << "embInit" << endl;

    return true;
  }
  // 구조체 메모리 할당
  bool embInit(unsigned int uiNum)
  {
    if(0 == uiNum)
    {
      return false;
    }
    // item에 동적 할당
    item = (int *)malloc(sizeof(int)*uiNum);
    // 동적 할당을 못 받았을 경우
    iNum = uiNum;
    cout << "embInit" << endl;
  
    return true;
  }
  // 구조체 메모리 초기화 함수
  void embFree()
  {
    free(item);
    item = 0;
    iNum = 0;
    cout << "embFree" << endl;
    return ;
      
  }  
};
int main()
{
  emb OBJ;
  // 자료의 추상화
  // OBJ에 함수만이 접근하고 main()은 접근하지 않는다.
  // 즉, 함수를 통해서 간접적으로 건드리게 하는데
  // 이것을 OBJ가 추상화 되었다. 자료가 추상화 되었다고 한다.
  OBJ.embInit(); // 0으로 초기화 하는 함수
  OBJ.embInit(300); // 동적할당 및 전체 계수(1200byte)
  OBJ.embFree();
  
  return 0;
}

 → 실행 결과

 → 같은 실행결과를 얻었다.
 → 인자로써 변수가 필요없어 불필요해진 코드들을 삭제함으로 써 소스의 양이 줄었다.
 → 오버로딩을 객체지향에서는 다향성이라고 한다.
 → 여기에 기능을 추가해서 클래스를 만든다.

 → 구조체는 메모리에 없다. 위의 소스에서 메모리에 올라간 것은 OBJ
 → 예를 들어 붕어빵과 틀을 생각해 보면 틀은 클래스 붕어빵은 객체(OBJ)이다.
 → 클래스의 관점에서는 붕어빵은 같고 객체의 관점에서는 다르다.
 → 즉, 인간이라는 카테고리를 클래스라 보면 각각의 우리는 서로 다른 객체이다.

 → 클래스를 만들 때 모델링을 한다. 모델링은 정의를 내리는 것이다.
 → 속성(attribute)
 ※ cmd 창에서 attrib 이라고 치면 해당 폴더 내의 파일 속성을 보여준다.


 → 머리색, 피부색은 상태(status)정보, 동작은 행위(behavior) 정보
 → 모든 속성은 상태와 행위(행동)을 가진다. 이를 위해 클래스는 변수 + 함수를 가지며 이를 통해 정보를 가진다.
 → 클래스를 만든다는 것은 분류한다는 것을 뜻한다.

 → 접근 속성
 → 위의 예제에서 struct를 class로 바꾸어 주고 
 → 변수는 private: , 함수는 public: 을 붙여주고 들여쓰기를 한뒤 컴파일 하면 에러없이 컴파일 한다.
 → 기본적으로 모든 속성은 private 이다. 이것은 클래스 안에서는 모두 사용할 수 있다.
 → public은 외부에서 접근이 가능하다.
#include<iostream.h>
#include<stdlib.h>


class emb // emb 클래스를 부른다.
{
  private :
    int *item;
    int iNum;  

  public :
    bool embInit();
    bool embInit(unsigned int uiNum);
    void embFree();
};
int main()
{
  emb OBJ;
  OBJ.embInit(); // 0으로 초기화 하는 함수
  OBJ.embInit(300); // 동적할당 및 전체 계수(1200byte)
  OBJ.embFree();
  
  return 0;
}
// 스코프 연산자를 붙인다.
bool emb::embInit()
{  
  item = 0;
  iNum = 0;
  cout << "embInit" << endl;
  return true;
}
bool emb::embInit(unsigned int uiNum)
{
  if(0 == uiNum)
  {
    return false;
  }
  item = (int *)malloc(sizeof(int)*uiNum);
  iNum = uiNum;
  cout << "embInit" << endl;

  return true;
}
void emb::embFree()
{
  free(item);
  item = 0;
  iNum = 0;
  cout << "embFree" << endl;

  return ;
}


 → 일반적으로 속성끼리 모아 쓰며, 변수 함수끼리도 각각 모아 쓴다.
 → 클래스 내부에는 변수와 함수 선언만 있는 것이 바람직 하다.
 → 따라서 함수부분을 클래스에서 빼고 함수 선언만 넣어 둘 수 있는데 그냥 컴파일 하면 문제가 발생한다.
 → 왜냐하면 클래스의 밖에 있는 함수의 본체는 클래스 내부에 선언된 함수 원형과는 다르기 때문이다. 따라서 에러가 발생한다.
 → 이를 해결하기 위해서는 함수 본체의 이름 앞에 emb:: 를 붙여줘야 한다.(클래스 이름::)
 → 이렇게 하면 클래스 내부에 선언한 함수 원형이 밖에 있는 함수의 본체를 가리키는 것이 된다. (JAVA는 이 방법이 되지 않으며 클래스 내부에 함수 본체를 넣어야 한다.)

 → 다음으로 위의 예제를 각각 emb.h, emb.cpp, main.cpp 3개의 파일로 분리한다. 

#ifndef __EMB_H_
#define __EMB_H_

#include
<iostream.h>
#include<stdlib.h>

class emb // emb 클래스를 부른다.
{
  
private :
    
int *item;
    
int iNum;  

  
public :
    
bool embInit();
    
bool embInit
          (
unsigned int uiNum);
    
void embFree();
};

#endif

#include"emb.h"

// 스코프 연산자를 붙인다.
bool emb::embInit()
{  
  item 
= 0;
  iNum 
= 0;
  cout 
<< "embInit" << endl;
  
return true;
}
bool emb::embInit(unsigned int uiNum)
{
  
if(0 == uiNum)
  {
    
return false;
  }
  item 
= (int *)malloc
              (
sizeof(int)*uiNum);
  iNum 
= uiNum;
  cout 
<< "embInit" << endl;

  
return true;
}
void emb::embFree()
{
  free(item);
  item 
= 0;
  iNum 
= 0;
  cout 
<< "embFree" << endl;

  
return ;
}

#include<iostream.h>
#include<stdlib.h>
#include"emb.h"

int main()
{
  emb OBJ;
  OBJ.embInit(); 
// 0으로 초기화 하는 함수
  OBJ.embInit(300); // 동적할당 및 전체 계수(1200byte)
  OBJ.embFree(); // 메모리 해제
  
  
return 0;
}


 → 위와 같이 파일을 나누어 컴파일 할 수 있다.
 → 보통 클래스는 헤더 파일(emb.h) 함수는 함수끼리 모아서(emb.cpp), 그리고 메인 함수(main.cpp)로 구분한다.


- inline (사용법, 특징)
 → 함수의 이름 앞에 inline을 붙일 수 있다.
 → inline bool emb::embInit() 이라고 사용
 → 이렇게 사용하면 원래는 main에서 호출될때 사용하는 함수의 본체가 main함수 안으로 직접 써지게 된다.
 → 따라서 실행파일의 크기가 커지나 실행속도가 빨라진다.
 → 클래스 안에서는 inline을 사용하지 않는다. 클래스 안에서 함수를 만들면 자동 inline 되기 때문.
 → 따라서 inline 함수는 무조건 헤더 파일에 들어가야 한다.(함수 본체가 밖에 있으면 함수 본체도 헤더에)
 → 메크로 함수보다 유리하다.


- 전역 변수의 사용
 → 만약 함수의 변수가 iNum=0; 이고 전역변수의 이름도 iNum =  100; 이라면 함수내에서 iNum의 값을 읽을 때 0이 읽어진다.
 → 하지만 ::iNum이라고 호출하면 전역변수 100이 읽어진다. 즉, 스코프 연산자를 앞에 붙여서 같은 이름을 가진 전역 변수도 호출이 가능하다.
 → 예제
#include<iostream.h>
#include<stdlib.h>

// 객체의 크기는 힙이나 스택에서 얼마만큼의 크기를 가지느냐다
// 따라서 아래 emb 클래스의 크기는 8byte이다.
// 포인터 변수 4byte, int형 4byte
// 함수는 코드영역이므로 호출되었다 사라지므로 용량이 적용 안된다.

class emb // emb 클래스를 부른다.
{
  private :
    int *item;
    int iNum;  

  public :
    bool embInit();
    bool embInit(unsigned int uiNum);
    void embFree();
};
int iNum = 100;

int main()
{
  emb OBJ;
  //OBJ.iNum = 100; // 접근속성이 pivate 이므로 에러
  OBJ.embInit(); // 0으로 초기화 하는 함수
  OBJ.embInit(300); // 동적할당 및 전체 계수(1200byte)
  OBJ.embFree();
  
  cout << "main의 iNum = " << iNum << endl;
  cout << "class emb의 크기 = " << sizeof(emb) << endl;
  
  return 0;
}
// 스코프 연산자를 붙인다.
bool emb::embInit()
{  
  item = 0;
  iNum = 0;
  cout << "embInit" << endl;
  cout << "embInit() 내부 변수 iNum = " << iNum << endl;
  // 스코프(::)를 변수 이름앞에 붙이면 전역 변수 iNum을 출력한다.
  // 만약 class에 iNum이 있더라도 pivate 이므로 스코프로 출력할 수 없다.
  cout << "embInit()에서 전역 변수 iNum = " << ::iNum << endl;
  return true;
}
bool emb::embInit(unsigned int uiNum)
{
  if(0 == uiNum)
  {
    return false;
  }
  item = (int *)malloc(sizeof(int)*uiNum);
  iNum = uiNum;
  cout << "embInit" << endl;

  return true;
}
void emb::embFree()
{
  free(item);
  item = 0;
  iNum = 0;
  cout << "embFree" << endl;

  return ;
}

 → 실행 결과


 → 마지막에 출력한 class emb의 크기를 살펴보면 8byte인 것을 확인할 수 있다.
 → 즉, 포인터 형의 크기 4바이트와 int형 크기 4바이트가 합쳐진 것으로, 함수는 사이즈에 포함되지 않음을 확인할 수 있다.


 → 함수의 인자와 내부변수의 이름이 같을 때 우선순위는 우선 함수 함수 안에 쓰여진 변수(내부 변수)가 높다.
 → 따라서 객체 자체가 자기 자신을 가리키는 포인터(자바는 참조)가 존재한다.
 → this-> or *this. 로 사용할 수 있다.
 ※ 참조 (자바는 기본적으로 참조형이다.)
 → 자바에서 기본형 int, char, short... 등이며 객체 참조 형은 변수(참조)가 객체를 가리키는 형태
 → 이것은 포인터와 같은 개념이며 자바는 기본형 빼고 전부 포인터 객체 참조와 같은 원리이다.

- 알리아스(메모리를 차지 하지 않는다.)
#include<stdio.h>

int
 main()
{
  int A = 100;
  int *P = &A;
  int &= A;
  
  printf("A = %X\n", A);  
  printf("P = %X\n", P);  
  printf("R = %X\n", R);  

  printf("A address = %X\n"&A);  
  printf("P address = %X\n"&P);  
  printf("R address = %X\n"&R);  
  
  R = 99;
  
  printf("A = %X\n", A);  
  printf("P = %X\n", P);  
  printf("R = %X\n", R);  

  return 0;
}

 → 실행 결과


 → 변수 A와 R이 같은 주소와 값을 가지는 것을 확인할 수 있다.
 → 어셈블리 내에서 A와 R을 같이 취급(컴파일 단계)
 → 클래스를 만들 때 참조 변수가 필요하다.