Programming

[Object-oriented programming] Class

아란정 2024. 10. 16. 16:29

C의 절차지향에서 객체 지향으로 인식을 전환해야 합니다. 인식 전환의 방법을 나를 남보듯, 손님 대하듯이 - 제작자 마인드와 사용자 마인드를 분리하는 것입니다. (코드를 남보기 쉽게 짠다는 뜻) 왜냐면 6개월 후의 나는 코드를 이해할 수 없기 때문입니다.

클래스란 C 구조체에서 확정된 변수, 객체, 함수를 포함한 하나의 이다. C의 구조체는 함수를 포함할 수 없지만 C++는 가능하다.

- 객체란 변수들과 관련된 메서드들이 모여서 만든 하나의 꾸러미다.

- 메서드란 함수 형태로 클래스의 실제 동작과 상태를 구현하는 핵심 요소다.

| 메서드랑 함수는 뭐가 다르고, 변수랑 함수가 모인 객체는 클래스랑 뭐가 다른가요? 왜 용어를 이렇게 여러 개 사용하시나요?

객체 지향의 패러다임을 설명하기 위해 세분화된 것입니다.

• 함수는 독립적으로 작동하는 코드 블록인 반면, 메서드는 객체 내에서 동작하기 때문에 구분이 필요합니다. 메서드는 특정 객체(클래스)에 소속되어 있다는 점이 가장 큰 차이점입니다. 메서드는 객체 호출 을 통해 호출되며 멤버 함수라고도 합니다.

• 객체는 실제 동작하는 단위이고, 클래스는 그 객체를 만들기 위한 설계도이므로 역할이 다릅니다. 객체에는 데이터(속성)과 동작(메서드)가 정의된 단위로 특정 클래스의 인스턴스(실제 예시) 이다. 객체는 클래스에 정의된 속성과 메서드를 사용한다.

인터페이스

사용자가 사용자 정보를 확인할 때마다 printf("%d, %s\n", user.nAge, usern.nName) 를 입력해야 한다고 생각해보자. 제작자가 만든 자료 구조의 멤버와 구성을 알아야만 작성할 수 있는 코드다. 사용자에게 편한 제공을 위해 인터페이스 함수 를 사용한다.

객체 내부 구조, 상호작용을 몰라도 인터페이스를 통해 사용자는 객체 기능을 사용할 수 있다.

  • 사람(클래스)는 달리기(메서드)가 있다.
  • 자동차(클래스)는 핸들(메서드)로 조작할 수 있다.
    라는 맥락에서 메서드(멤버 함수)는 인터페이스 함수라고도 한다.
typedef structure USERDATA
{ 
    int nAge;
    char nName[32];
    void(*Print)(struct USERDATA *);
} USERDATA;

void PrintData(USERDATA *pUser)
{ printf("%d, %s\n", pUser->nAage, pUser->nName); }

// C
int main(void)
{
    USERDATA user = {20, "tistory", PrintData};
printf("%d, %s\n", user.nAge, user.nName);
PrintData(&user);
user.Print(&user); // 멤버 변수에 선언된 프린트 포인터에 void PrintData를 지정하였다 : PrintData를 가리키고 있다. 
                    // 생성된 사용자 객체의 메모리 주소를 Argument로 넣어 void PrintData가 실행된다. 

// CPP
user.Print();

C에서 구현되는 user.Print(&user)은 user의 멤버가 user의 주소를 몰라 &user로 전달하는 방식이다. 자녀가 부모의 이름을 모르는 격이다.
\C에선 구조체 안에 함수를 넣을 수 없다.
\CPP는 멤버 함수는 클래스 내부에서 선언하고 정의할 수 있다.

보통은 가독성을 위해 분리해서 쓴다.

이를 CPP에서 user.Print(); 로 작성한다. 표현만 이렇고 실제로 &user 매개변수가 존재한다.
- this 포인터 이해에 도움이 된다.

클래스의 멤버 변수 사용

class CLASS
{
public:
    CLASS() { 
    std::cout << "Constructor" << std::endl;

    // 인스턴스가 생성되면 멤버 데이터 자동 초기화
    // c++ 에선 선언과 동시에 멤버 변수 초기화가 가능하다. 
    age = 5;
    name = 'charles';
    }

    ~CLASS() { std::cout << "Destructor" << std::endl; }

    // 멤버 데이터
    int age;
    char name[20];

    void Print(void)
    {
    printf("%d, %s\n", age, name); // 멤버 데이터 사용
    }

    void printdata(void); // separate definitions and declaration
};

void CLASS::printdata(void)
{
 std::cout << age << std::endl; //still possible to access member data
 }

int main(int argc, char* argv[])
{
  CLASS class; // 클래스 객체(인스턴스) 생성

  return 0;
 }

Print 가 사용하는 argument 는 local variable 이 아니라 멤버 함수 Print가 속한 클래스의 멤버 변수이다. 즉, 멤버 데이터에 접근하고 값을 출력할 수 있다. 데이터에 접근 제어자는 public, protected, private가 있다.

  • 멤버 함수는 기본적으로 지역 변수 > 멤버 변수 > 전역 변수 순으로 식별자를 검색한다.
  • 기본값은 private -> 데이터 접근을 위해선 public을 통해야 한다.
private:
    int data;
public:
    Class(){}; // constructor could be private so be careful
    int Getdata(void) {return data;}
    void Setdata(int nParam) { data = nParam }; 

Default constructor

컴파일러가 알아서 만들어 넣는 친구들: 생성자와 소멸자가 없는 클래스는 없다.
\생략하면 컴파일러가 알아서 만들어준다.

constructor
Initialize member variables(data)

  • no return type
  • automatic calling by the compiler
    생성자는 overloading할 수 있다.
  • 참조형 멤버는 반드시 생성자 초기화 목록을 이용해 초기화해야 한다.
public:
    CLASS(int num) : age(num) {cout << "CLASS()" << endl;}
    CLASS(int &rParam) : data(rParam) {};
    // {data = rParam;} 은 선언 및 정의가 아닌 단순 대입 연산이므로 컴파일 오류가 난다. 
    // argument에 참조자가 아닌 int rParam을 쓰면 쓰레기값이 생긴다. 

    // overloading
    CLASS(int x){age = 100;}
    CLASS(int x, int y) : CLASS(x) { } // set x to 100

private:
    int &data;

int main(){
    CLASS a(1);
    CLASS b(2);

    int a = 10;
    CLASS ref(a);

     return 0;
}

& 참조자의 내부는 포인터와 동일하다. 약간의 디테일 빼고

  • 대상 반드시 지정, 중간에 원본 변경 불가능
    참조자로 된 data(argument)가 매개변수로 참조자가 아닌 타입을 받으면, 함수 매개변수로 자동 메모리 할당되어 함수 블럭이 반환되면 pop되면서 소멸한다. 참조자가 가리킬 주소값이 사라지는 것이다. 결국 data는 사라질 대상에 대한 참조자고, 초기화된다.

Destructor

  • no return type
  • compiler call the function automatically
    can not define

실행 순서에 관하여

int main(int argc, char* argv[])
{
 cout << "Begin" << endl;
 CLASS A;
 cout << "End" << endl;

 return 0;
}

Begin
Constructor
End
Destructor

A는 main의 지역 변수이기 때문에 선언된 블록 범위가 끝나면 자동 소멸한다.

End가 Destructor 이전에 출력된다는 걸 헷갈리지 말자

C는 main(), CPP는 Global Variable 부터

CLASS A;

int main(int argc, char* argv[])
{
 cout << "Begin" << endl;
 cout << "End" << endl;

 return 0;
}

C++에서는 전역 변수로 선언한 클래스의 생성자가 main보다 먼저 호출된다.

Constructor
Begin
End
Destructor

Dynamic Allocation

CLASS *pclass = new CLASS;
CLASS *pclass = new CLASS[3];

delete [] pclass;
delete pclass;
  • 생성, 소멸 시점을 명확히 알 수 있다. Begin -> Con -> Des -> End
  • 배열 생성 객체는 배열로 삭제해야 한다. 칸 개수만큼 생성하기 때문이다.