C++/기초

C++ 기초 : 클래스 (1)

시플마 2024. 4. 13. 07:29

C++ 언어에서는 기존 C 언어에서는 없던

클래스라는 개념이 추가되었습니다.

 

클래스는 사용자가 정의하는 자료형입니다.

C 언어에서의 구조체와 같은 역할이죠.

 

물론 C++ 환경에서도 구조체를 사용할 수 있습니다.

C++ 문법에서는 구조체의 이름을 재정의하여 사용할

필요 없이 struct 키워드를 통해 바로 사용할 수 있습니다.

 

 

 


 

 

 

 

클래스도 구조체와 마찬가지로 멤버로 구성되는데

해당 멤버를 공개할지 공개하지 않을지 선택할 수 있습니다.

private로 선언된 멤버는 공개하지 않고,

public으로 선언된 멤버는 공개됩니다.

 

예를 들어

 

cMy라는 클래스를 정의하였고

해당 클래스의 멤버는 int형 변수 m_i와 float형 변수 m_f가 있습니다.

 

m_i는 private로, m_f는 public으로 선언되었죠.

 

이후 cMy형 객체 c를 선언하였습니다.

 

21번째 줄을 보시면 

객체 c를 통해 멤버 m_f에만 접근할 수 있는 것이 보이시나요?

 

m_f는 public이므로 직접 접근이 가능한 것이고

m_i는 private이므로 접근이 불가능한 것입니다.

 

 

왜 이런 기능이 존재하는 걸까요?

 

구조체로 가변 배열을 만들었다고 해 보죠.

 

tArr형(구조체) 가변 배열 arr을 만들었습니다.

 

그리고 PushBack 함수를 통해 arr에 10을 넣었습니다.

 

그러면서 tArr형 구조체의 멤버 변수 iCount의 값이 1이 되었습니다.

멤버 변수 iCount는 가변 배열이 갖고 있는 데이터의 개수를

나타내는 변수로, 데이터가 들어올 때마다 1이 오르게 

설계되어 있죠.

 

 

그런데 아래 코드처럼

 

객체 arr의 멤버 변수 iCount에 직접 접근해서

100을 넣으면 실제 데이터는 1개인데

데이터의 개수가 100개라고 표현되는 문제가 발생하죠.

 

만약 가변 배열이 구조체가 아닌 클래스로 선언되어

멤버 iCount를 private로 선언할 수 있었다면

이런 문제가 발생하지 않았을 겁니다.

 

 

 


 

 

 

 

클래스는 클래스 전용 함수를 만들 수 있습니다.

 

클래스 cMy의 전용 함수로 SetInt라는 함수를

만들었습니다. 값을 받아서 멤버 변수 m_i에

그 값을 대입하는 함수이죠.

 

해당 함수를 통해 10을 대입하자, 

m_i에 10이 대입된 것을 확인할 수 있습니다.

 

 

객체 c의 멤버 m_i는

 

private로 선언되었기 때문에

20번째 줄처럼 직접 값을 대입하는 게 불가능합니다.

 

그래서 SetInt라는 함수를 public으로 선언하여

m_i에 값을 넣을 수 있도록 하였죠.

 

 

전용 함수라고 표현한 이유는 

 

Test라는 함수에는 바로 접근이 가능합니다.

 

하지만 클래스 cMy의 멤버 함수 SetInt에는 

바로 접근하는 것이 불가능하죠.

 

 

그렇다면 

 

25번째 줄처럼

범위 지정 연산자(::)를 통해 cMy의 내부로 들어가

거기에 있는 SetInt 함수를 호출하면 되지 않을까 싶지만

이 역시 불가능합니다.

 

 

결국 31번째 줄처럼 SetInt 함수에 접근하려면 

cMy형(클래스)으로 선언되어 실체화된 객체(Instance)가 필요한 것입니다.

 

 

 

 


 

 

 

구조체로 선언된 tArr형 가변 배열은

 

초기화 함수를 따로 만들어서 초기화를 진행했죠.

 

하지만 이는 진정한 의미의 초기화는 아닙니다.

 

초기화는 객체가 생성되자마자 값이 들어가야 합니다.

하지만 tArr형 객체는 선언하고 나면 변수들이 생기고

초기화 함수를 통해 그 변수들에 값을 대입하는 방식이죠.

 

즉 구조체는 초기화 함수를 만들어서 멤버를

초기화해 주거나, 직접 하나씩 초기화를 진행해 주지

않으면 구조체들의 멤버의 값은 모두 NULL입니다.

 

값이 NULL인 멤버에 접근하여 값을 넣으려고 하면

문제가 발생합니다. 예를 들어 위의 코드에서

int형 포인터 pInt의 값이 NULL입니다.

즉 메모리의 주솟값이 없는 상태입니다.

여기에 값을 넣으려고 하면 문제가 발생하겠죠?

 

이러한 이유로 구조체는 초기화 함수를 만들거나 

직접 초기화를 진행하여 오류를 방지해야 합니다.

 

 

클래스에서는 위의 문제를 개선하여

'생성자'라는 초기화 함수를 제공합니다.

 

15 ~ 20번째 줄이 바로 생성자입니다.

 

cMy형 객체를 만들면

해당 객체의 멤버 m_i에 100을 대입하게 됩니다.

 

 

컴파일을 통해 확인해 보시죠.

 

25번째 줄이 실행되고 나서

28번째 줄 이전에 생성자가 호출될 것입니다.

 

cMy형 객체 c를 만드는 순간부터 보겠습니다.

 

25번째 줄에 중단점을 걸고 F11을 눌러

하나씩 실행해 보죠.

 

 

F11을 누르자 생성자로 가는 모습입니다.

 

 

이후 m_i에 100을 대입하라는

 

코드를 만나 m_i에 100이 대입된 것을 확인할 수 있네요.

 

 

28번째 줄에서

 

SetInt 함수를 만나 해당 함수로 이동하는 모습입니다.

 

 

m_i에 매개 변수 i의 값, 

 

즉 10을 대입하라는 코드를 만나 m_i에 10이 대입되었습니다.

 

 

디버깅 중 Ctrl + Alt + D를 누르면

 

디스어셈블리 코드를 볼 수 있습니다.

 

중단점이 걸려 있는 부분을 보시면

cMy c; 라는 코드가 보입니다. 우리가 작성한

코드죠. 근데 두 줄 밑에 call이라고 해서 cMy에 있는

cMy라는 함수를 호출한 게 보이시나요?(cMy::cMy)

 

이 부분이 바로 생성자를 호출한 부분입니다.

 

생성자를 호출하는 구문을 직접 적지는 않았지만

컴파일러가 객체를 생성하는 코드를 보면

C++ 문법 규칙에 의해 "클래스로 만든 자료형을

가진 객체를 생성하면 생성자를 호출해야지."라고 

판단합니다. 그래서 생성자를 호출하는

코드를 알아서 추가하고 실행한 거죠.

 

 

하지만 이 또한 초기화라고 할 수 없습니다.

 

결국 객체 c가 만들어지고

이후에 m_i에 100을 대입한 것이니까요.

 

"객체가 만들어지고 그 공간으로 가 100을 넣음."

이 아니라 "객체가 만들어지면서 100을 넣음."이 

되어야 하는 것이죠.

 

이를 위해 생성자에서는 특수하게 '이니셜라이져'

라는 문법을 제공합니다.

 

아래 코드처럼

 

생성자 옆에 : (콜론)을 붙이고 멤버명(초기화값)을 

써 주시면 됩니다. 멤버가 여러 개일 경우 콤마(,)를 

통해 구분하여 작성해 주시면 됩니다.

 

이니셜라이져를 통해 초기화해야 진정한 의미의 초기화라고 할 수 있죠.

 

 

 


 

 

 

메모리를 동적 할당했으면 종료 전에

메모리 해제도 해주어야 메모리 누수가 발생하지 않습니다.

 

그래서 

 

구조체를 통해 가변 배열을 구성했을 때에도

ReleaseArr이라는 함수를 통해 동적 할당한

메모리를 해제해 주었죠.

 

 

클래스는 초기화뿐만 아니라

이런 메모리 해제를 위한 함수도 제공합니다.

 

바로 '소멸자'라는 것인데요.

 

소멸자는 클래스 이름에 물결을 붙여 만들 수 있습니다.

 

메모리 해제를 위한 함수이다 보니, main 함수가 종료될 때

호출이 됩니다. main 함수가 종료되는 시점인 37번째 줄에

중단점을 걸고 F11을 눌러 한 줄을 실행하니 

소멸자로 이동하는 것이 보이네요.

 

 

디스어셈블리 코드도 보겠습니다.

 

main 함수가 종료되는 시점인 

return 0; 밑에 드래그한 줄을 보면

클래스 cMy에 있는 ~cMy 함수를 호출한 게 보입니다.

소멸자를 호출한 것이죠.

 

마찬가지로 소멸자를 직접

호출하지는 않았지만 컴파일러가 자동으로

 

"객체가 선언되어 있네. main 함수가 종료되면서

스택 영역이 정리될 때 지역 변수인 객체들도

사라질테니까 메모리 해제를 위한 소멸자를 호출해야겠다."

 

라고 판단해 소멸자 구문을 추가하고 실행한 것이죠.

 

 

 


 

 

 

클래스로 가변 배열을 만든다면 구조체로 가변 배열을

만들었을 때와 비교하여 많은 부분이 개선되었을 겁니다.

 

초기화 함수를 만들지 않고도 초기화를 진행할 수 있고

반드시 신경 써야 하는 메모리 해제 부분도 소멸자로 

어느 정도 해소가 되기 때문이죠.

 

 

생성자와 소멸자는 C++ 문법에서

반드시 존재해야 합니다.

 

클래스로 만든 자료형으로 객체를 만들 때

반드시 생성자를 호출하게 되어 있고

main 함수가 종료되면서 해당 객체가 사라질 때 

소멸자를 호출하게 되어 있기 때문이죠.

 

 

그래서 아래 코드처럼

 

클래서 cMy의 생성자와 소멸자 부분을

지워도 컴파일 과정에서 알아서 생성자와

소멸자를 만들고 실행합니다. 이렇게 자동으로 

만들어진 생성자를 기본 생성자(디폴트 생성자),

소멸자를 기본 소멸자(디폴트 소멸자)라고 합니다.

 

 

 

 

강의 출처 : https://www.youtube.com/watch?v=PFc4g8mxOiI&list=PL4SIC1d_ab-aOxWPucn31NHkQvNPHK1D1&pp=iAQB