[Object-oriented programming] Class
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
- 배열 생성 객체는 배열로 삭제해야 한다. 칸 개수만큼 생성하기 때문이다.