티스토리 뷰

클래스

객체 지향의 시작

 

이번에는 클래스와 객체라는 개념을 알아보자.

 


클래스란 무엇인가?

클래스란 실체가 없고 정보만 담고 있는 코드

 

"정보만 담고 있다."라는 말이 무엇일까.

 

프로그래밍을 하다보면, 구조체 혹은 리스트, 배열과 같은 자료구조까지 특정 정보들을 모으는 컨테이너들을 접해보았을 것이다.

 

클래스도 개념은 크게 다르지 않다. 클래스는 멤버변수, 다형성, 정적 멤버 등 다양한 정보를 선언 및 저장할 수 있고 함수, 접근 제어, 상속, 생성자, 소멸자와 같은 기능을 제공할 수도 있다.

 

그런데 어디서 많이 들어본 이야기이지 않은가? 바로 구조체와 굉장히 유사한 점이 많다는 것이다.

 

구조체와 클래스

우선 구조체와 클래스를 비교해보자. (추가로 메모리 할당량까지☆)

  C++ 구조체 C++ 클래스
기본 접근 제어자 public private
typedef 선언 가능 선언 가능
(C++ 표준에서 권장하지 않음 (컴파일러 차이))
기능 멤버 변수
멤버 함수
접근 제어
상속
생성자와 소멸자
정적 멤버
다형성
기타 등등

주의) C와 C++는 차이가 많이 없지만, C#과 C++의 차이는 다소 있음

 

위 표를 보면 구조체와 클래스의 차이가 거의 없는 것을 볼 수 있다.

이는 실제로 C에서 사용하던 구조체라는 개념을 C++에서도 호환하기 위해 거의 비슷하게 작성되는 것이다.

 

C#으로 넘어간다면, 생각 이상으로 다수의 차이점이 생긴다. 하나 꼽자면 C#에서는 기본적으로 구조체는 CallByValue이고, 클래스는 CallByReference, 구조체는 스택영역, 클래스는 힙영역에 선언된다 정도가 크게 지목할 만한 것이다.

 

예제 코드

#include <iostream>

using namespace std;


struct MyStruct1 {
	int i = 0;
    char c = 'a';
    double d = 0;
};

class MyClass {
public:
    int i = 0;
    char c;
    short s = 0;
    double d = 0;
};

void CallStruct(MyStruct1 ms1) {
    cout << endl;
    cout << "Struct CallByValue : " << &ms1 << endl;
    cout << "Struct integer Before : " << ms1.i << endl;
    ms1.i++;
    cout << "Struct integer After : " << ms1.i << endl;
}
void CallStructAddress(MyStruct1 *ms1) {
    cout << endl;
    cout << "Struct CallByAddress : " << ms1 << endl;
    cout << "Struct Address integer Before : " << ms1->i << endl;
    ms1->i++;
    cout << "Struct Address integer After : " << ms1->i << endl;
}
void CallStructReference(MyStruct1 &ms1) {
    cout << endl;
    cout << "Struct CallByReference : " << &ms1 << endl;
    cout << "Struct Reference integer Before : " << ms1.i << endl;
    ms1.i++;
    cout << "Struct Reference integer After : " << ms1.i << endl;
}

void CallClass(MyClass mc) {
    cout << endl;
    cout << "Class CallByValue : " << &mc << endl;
    cout << "Class integer Before : " << mc.i << endl;
    mc.i++;
    cout << "Class integer After : " << mc.i << endl;
}
void CallClassAddress(MyClass *mc) {
    cout << endl;
    cout << "Class CallByAddress : " << mc << endl;
    cout << "Class integer Before : " << mc->i << endl;
    mc->i++;
    cout << "Class integer After : " << mc->i << endl;
}
void CallClassReference(MyClass &mc) {
    cout << endl;
    cout << "Class CallByReference : " << &mc << endl;
    cout << "Class integer Before : " << mc.i << endl;
    mc.i++;
    cout << "Class integer After : " << mc.i << endl;
}

int main(void) {

    MyStruct1 ms1;
    MyClass mc;

    cout << "Struct Pointer : "<< &ms1 << endl;
    CallStruct(ms1);
    CallStructAddress(&ms1);
    CallStructReference(ms1);
    cout << "Integer After Call : " << ms1.i << endl;

    cout << endl;

    cout << "Class Pointer : "<< &mc << endl;
    CallClass(mc);
    CallClassAddress(&mc);
    CallClassReference(mc);
    cout << "Integer After Call : " << mc.i << endl;

    return 0;
}
Struct Pointer : 0x61ff00		// 구조체 주소

Struct CallByValue : 0x61fec0		// 새로운 구조체 전달
Struct integer Before : 0
Struct integer After : 1

Struct CallByAddress : 0x61ff00		// 기존 구조체 사용
Struct Address integer Before : 0
Struct Address integer After : 1

Struct CallByReference : 0x61ff00	// 기존 구조체 사용
Struct Reference integer Before : 1
Struct Reference integer After : 2
Integer After Call : 2

Class Pointer : 0x61fef0		// 클래스 주소

Class CallByValue : 0x61fec0		// 새로운 클래스 전달
Class integer Before : 0
Class integer After : 1

Class CallByAddress : 0x61fef0		// 기존 클래스 사용
Class integer Before : 0
Class integer After : 1

Class CallByReference : 0x61fef0	// 기존 클래스 사용
Class integer Before : 1
Class integer After : 2
Integer After Call : 2

 

 


 

클래스 선언

클래스를 선언하는 법은 구조체를 선언하는 것과 같다. 다음은 기본적인 클래스를 구성하는 요소들이다.

주요한 것들만 좀더 확인하고 가자.

// 클래스 선언
class MyClass {
public:	// 접근 제어자 설정
    int myInt;	// 멤버 변수 선언

    void myFunction();	// 멤버 함수 선언

    MyClass(); // 생성자 선언
    ~MyClass(); // 소멸자 선언
};
'class' 키워드 클래스 임을 선언하는 키워드
클래스 명칭 클래스의 이름 정의
접근 제어자 설정 클래스 내에 선언되는 모든 정보에 대한 접근 제어를 설정한다.
멤버 변수 클래스 내의 데이터 멤버이며, 초기값을 주거나, 나중에 함수 내에서 초기화할 수 있다.
멤버 함수 클래스 내의 함수 멤버는 클래스가 수행할 수 있는 기능들을 정의하는 것이며, 함수의 정의는 나중에 클래스 외부에서 구현될 수 있다.
생성자 클래스의 객체가 생성될 때 호출되는 특별한 멤버 함수이며, 클래스의 멤버 변수를 초기화하는 데 사용
소멸자 클래스의 객체가 소멸될 때 호출되는 특별한 멤버 함수이며, 클래스의 리소스를 정리하는 데 사용
';' 작성 클래스 선언이 끝났음을 알리며, typedef와 사용한다면 별칭을 선언할 수 있는 위치를 표시

 

 

접근 제어자 (Access Control)

객체가 가진 정보는 지켜져야 한다.

 

접근 제어자는 C++의 4대 속성 중 하나 캡슐화 (※추상다캡)의 기능이며, 정보 은닉을 통한 특정 멤버나 함수에 대한 외부 접근을 제한하는 것을 말한다.

 

아래 3가지 접근 제어자를 통해 우리는 클래스에서 선언되는 모든 정보들을 외부에 노출시킬지 결정할 수 있다.

 

접근 제어자
public protected private
모든 외부 접근 허용 상속 관계의 접근만 허용 모든 외부 접근 차단

 

 

Public 접근
class MyClass {
public:
    int i = 0;
    char c = 'a';
    short s = 0;
    double d = 0;
    
    void Printing() { cout << "Hello" << endl; }
};

int main(void) {
    MyClass mc;
    
    mc.i = 3;		// 가능
    mc.d = 10;		// 가능
    
    mc.Printing(); // 가능
    
    return 0;
}

 

우선 public 제어자는 말그대로 다른 모든 곳에서 사용가능한 정보들을 선언한다는 것이다.

 

이는 멤버 함수도 마찬가지로 public 제어자 아래 선언된 멤버 함수는 외부에서 호출되어 특정 기능을 수행하고 값을 반환하거나 해당 값을 클래스 내부에 적용시키는 등의 요청을 처리할 수 있습니다.

 

public은 데이터에 직접 접근이 가능해지기 때문에 굉장히 편리하다. 단, 원치 않은 데이터 변형이 일어날 수 있다.

이를 방지하기 위해, Getter와 Setter 같은 프로그래밍 스타일로 간접 접근을 허용하기도 한다. (주의할 것은 무분별한 Getter와 Setter 선언이 불필요한 스택 프레임을 쌓을 수 있다.)

 

class MyClass {
private:
    int myData;

public:
    int getMyData() const {
        return myData;
    }
    void setMyData(int newData) {
    	if (newData > 0)
        	myData = newData;
    }
};

 

 

Private 접근
class MyClass {
private:
    int i = 0;
    char c = 'a';
    short s = 0;
    double d = 0;
    
public:
    void Printing() { cout << "Hello" << endl; }
};

int main(void) {
    MyClass mc;
    
    // mc.i = 3;		// 에러: 불가능
    // mc.d = 10;		// 에러: 불가능
    
    mc.Printing(); // 가능
    
    return 0;
}

 

private 제어자는 그 어떠한 외부 접근도 차단한다.

단, 같은 클래스 내부의 다른 변수나 함수들은 private으로 선언된 데이터들에 대한 모든 접근이 가능하다.

 

 

Protected 접근
class BaseClass {
protected:
    int protectedMember;

public:
    void accessProtectedMember() {
        protectedMember = 42; // BaseClass 내부에서는 직접 접근 가능
    }
};

class DerivedClass : public BaseClass {
public:
    void modifyProtectedMember() {
        protectedMember = 10; // DerivedClass에서도 protected 멤버에 접근 가능
    }
};

int main() {
    BaseClass obj1;
    DerivedClass obj2;

    // obj1.protectedMember = 42;  // 에러: 외부에서 직접 접근 불가능
    // obj2.protectedMember = 10;  // 에러: protected로 상속되었지만 파생 클래스 외부에서 직접 접근 불가능

    obj1.accessProtectedMember();  // 정상: 멤버 함수를 통한 접근은 가능
    obj2.modifyProtectedMember();  // 정상: 파생 클래스에서 멤버 함수를 통한 접근은 가능

    return 0;
}

 

protected 제어자는 상속 받은 자식에게만 접근을 허용한다.

상속이라는 개념은 이번 챕터에서 다루지 않기에 크게 말하지 않고 넘어갈 것이다.

 

 

 

멤버 변수와 함수

멤버 변수와 함수는 클래스나 구조체 내부에 생성되는 변수와 함수들을 말한다.

 

이 데이터들은 컴파일이 실행될 때, 크기가 결정된다. 단, 객체의 크기가 결정되는 것은 아니고 해당 객체가 생성될 때, 얼마나 많은 메모리를 할당해야하는지 결정된다.

 

📌 멤버 변수의 크기
멤버 변수의 크기는 데이터 타입에 결정된다.
클래스 객체의 크기 또한 멤버 변수들의 크기에 결정되고 컴파일러는 일반적으로 멤버 데이터를 메모리에 정렬하여 메모리 접근을 최적화 하려 한다.
📌 멤버 함수의 크기
멤버 함수는 기계어 코드로 구성된다. 즉, 멤버 변수는 모든 객체가 동일하게 가지고 있어야하는 데이터인 반면, 멤버 함수는 모든 객체가 공통으로 사용하는 코드이기에 코드 영역에 자리잡고 있다.
이를 통해, 코드 영역에 있는 함수들은 sizeof함수에 계산되지 않는다.

 

 

생성자와 소멸자

생성자와 소멸자는 클래스를 통해 새로운 객체를 생성할 때와 기존 객체가 제거될 때, 자동으로 실행되는 함수로 대부분 필수적인 초기화 작업과 소멸 전 데이터를 정리하는 것에 사용된다.

 

더욱 자세한 내용은 다른 글에 "생성자와 소멸자"라는 이름으로 기제할 예정이다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함