[C++] 클래스의 완성

C++ 2017. 12. 23. 20:17

1] 정보 은닉
: 멤버 변수에 직접 접근하는 것을 허용하지 않고 setter, getter를 통해 접근하도록 하는 것이다. 
이는 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야 하고, 또 실수를 했을 때 실수가 쉽게 발견 될수 있도록 한다. 

1. const 함수 
특히 아래와 같은 종류들의 함수에 const 함수를 적용할 수 있을 것이다.

int GetX() const;
int GetY() const;
void ShowRecInfo() const;

위 함수들은 이름으로도 알 수 있듯이 멤버변수를 단순히 get, 또는 show 하는 용도로 쓰이는 함수들이다. 그러니 멤버 변수의 변경이 일어나면 안되는 종류의 함수들이다. 그래서 이러한 함수 안에서는 멤버변수의 변경이 불가하도록 하는 장치가 "const" 키워드이다. 

또한 const 함수 내에서는 const가 아닌 함수의 호출이 제한된다!

그리고 매개변수에도 "const" 키워드를 추가할 수 있다. 

void InitNum(const EasyClass &east)
{
    num=easy.GetNum();
}

class EasyClass
{
    public:
    int GetNum()  // const 선언이 추가되어야 컴파일 에러 소멸.
    {
        return num;
    }   
}

매개 변수의 const 선언은 참조자를 대상으로 값의 변경 능력을 가진 함수의 호출을 허용하는 것을 막는다. 


2] 캡슐화(Encapsulation)
: 캡슐화는 관련 있는 함수와 변수를 하나의 클래스 안에 묶는 것이다. 별로 어렵지 않게 느껴질 수 있지만, 캡슐화는 어려운 개념이다. 왜냐하면 캡슐화의 범위를 결정하는 일이 쉽지 않기 때문이다. 


3] 생성자(Constructor)와 소멸자(Destructor)
1. 생성자의 이해
- 클래스의 이름과 생성자의 이름이 동일해야 한다. 
- 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
- 생성자도 함수의 일종이니 오버로딩이 가능하다. 
- 생성자도 함수의 일종이니 매개변수에 디폴트 값을 설정할 수 있다.

class SimpleClass
{
private:
    int num1;
    int num2;
public:
    SimpleClass(int n1) // 생성자
    {
        num1 = n1;
        num2 = 2;
    }

    SimpleClass(int n1, int n2)
    {
        num1 = n1;
        num2 = n2;
    }

    int GetNum1() const
    {
        return num1;
    }

    int GetNum2() const
    {
        return num2;
    }
}

* 함수의 원형을 지역적으로 선언

int main(void)
{
    SimpleClass sc1(); // 함수의 원형 선언!
    SimpleClass mysc = sc1();
    
    return 0;
}

SimpleClass sc1()
{
    SimpleClass sc(20, 30);
    
    return sc;
}

2. 멤버 이니셜라이저를 이용한 멤버 초기화

아래는 Point 클래스 선언이다.

class Point 
{
private:
    int x;
    int y;
public:
    Point(const int &xpos, const int &ypos); // 생성자
    int GetX() const;
    int GetY() const;
    bool SetX(int xpos);
    bool SetY(int ypos);
}

아래는 Point 클래스의 생성자 정의다.

Point::Point(const int &xpos, const int &ypos)
{
    x = xpos;
    y = ypos;
}

아래는 Rectangle 클래스의 선언이다.

class Rectangle
{
private:
    Point upLeft;
    Point lowRight;
public:
    Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
};

아래는 Rectangle 클래스의 생성자 정의다. 

Rectangle:Rectangle(const int &x1, const int &y1, const int &x2, const int &y2) : upLeft(x1, y1), lowRight(x2, y2)
{
    //empty
}

이 중에서 :upLeft(x1, y1), lowRight(x2, y2) 가 멤버 이니셜라이저다. 
풀어서 설명하면,
- 객체 upLeft의 생성과정에서 x1과 y1을 인자로 전달받는 생성자를 호출하라
- 객체 lowRight의 생성과정에서 x2와 y2를 인자로 전달받는 생성자를 호출하라

위 생성자 호출시 실행 순서는 멤버 이니셜라이저가 먼저 호출되고 생성자의 몸체가 나중에 실행된다. 

3. 멤버 이니셜라이저를 이용한 변수 및 const 상수(변수) 초기화
: 멤버 이니셜라이저는 객체가 아닌 멤버의 초기화에도 사용할 수 있다. 

class SoSimple
{
private: 
    int num1;
    int num2;
public:
    SoSimple(int n1, int n2) : num1(n1)
    {
        num2 = n2;
    }
};

이와 같이 객체가 아닌 멤버 변수도 멤버 이니셜라이저로 초기화 할 수 있다. 그리고 멤버 변수들을 초기화 할때 생성자에서 하는것 보다 멤버 이니셜라이저를 통하는것이 더 권장된다. 그 이유는 다음과 같다.
- 초기화의 대상을 명확히 인식할 수 있다.
- 성능에 약간의 이점이 있다.

두번째 이유에 대해서 조금 더 설명하자면, 위 예재에서 num2를 생성자에서 초기화 하고 있는데 이는 바이너리 코드가 구성될 때 아래와 같이 구성된다.

int num2;
num2 = n2;

멤버 이니셜라이저를 이용해 초기한 num1의 경우는 다음과 같이 구성된다. 

int num1 = n1;

위와 같은 관점에서 const 멤버 변수에 대해서도 생각해보면 const 멤버 변수는 생성과 동시에 초기화가 되어야만 한다.
그래서 const 멤버 변수도 멤버 이니셜라이저를 통해 초기화가 가능하다.

* const 변수와 마찬가지로 참조자(reference)도 선언과 동시에 초기화가 이루어져야 한다.
따라서 이니셜라이저를 이용하면 참조자도 멤버변수로 선언될 수 있다. 

4. 디폴트 생성자
: 모든 클래스는 하나 이상의 생성자를 가지고 있다. 프로그래머가 생성자를 따로 정의하지 않을 경우 컴파일러가 디폴트 생성자를 하나 만들어 준다.
디폴트 생성자는 매개변수를 하나도 받지 않고 아무 행동도 하지 않는 생성자이다. 
프로그래머가 생성자를 하나라도 따로 정의한 경우 디폴트 생성자는 만들어지지 않는다. 

* malloc을 통한 동적 할당
: malloc을 통해 객체의 메모리를 동적으로 할당 했을 경우 생성자가 불리지 않는다.

AAA *ptr = (AAA *)malloc(sizeof(AAA));

위 코드에서 보듯이 메모리를 할당할 때 AAA 객체의 크기만 받기 때문에 생성자를 호출해 줄리 없다. 따라서 C++ 객체를 생성할 경우에는 "new" 를 통해 생성해야 한다. 

5. private 생성자 
: 클래스 내부에서만 객체의 생성을 허용하려는 목적으로 생성자를 private으로 선언하기도 한다. 

class AAA
{
private: 
    int num;
public:
    AAA& createInitObj(int n) const
    {
        AAA *ptr = new AAA(n);
        
        return *ptr;
    }   
private:
    AAA(int n) : num(n) {}
}

위 예제에서는 힙 영역에 생성된 객체를 참조(reference)의 형태로 반환하고 있다.
이는 앞서 설명한 "힙에 할당된 메모리 공간은 변수로 간주하여, 참조자를 통한 참조가 가능하다" 라는 사실을 다시 한번 확인시켜 준다. 

6. 소멸자의 이해와 활용 
: 소멸자는 대게 생성자에서 할당한 리소스의 소멸에 사용된다. 예를 들어서 생성자 내에서 new 연산자를 이용해서 할당해 놓은 메모리 공간이 있다면, 소멸자에서는 delete 연산자를 이용해서 이 메모리 공간을 소멸한다. 

- 클래스의 이름 앞에 "~"가 붙은것이 소멸자다. 

4] 클래스와 배열 그리고 this 포인터 
1. 객체 배열
: SoSimple 클래스의 배열의 선언은 다음과 같다. 

SoSimple arr[10];

동적 할당하는 경우에는 다음과 같다.

SoSimple *ptrArr = new SoSimple[10];

위와 같이 일괄적으로 10개의 객체를 배열로 선언하면 각 객체에 생성자가 불리면서 객체가 만들어지는데, 생성자에 매개변수를 전달하지 못한다. 
그래서 위와 같이 선언하려면 아래와 같은 생성자가 필히 존재해야 한다. 

SoSimple() { ... }

2. 객체 포인터 배열 

Person *parr[2];

parr[0] = new Person("sung gon", 19);
parr[1] = new Person("gil dong", 25);

delete parr[0];
delete parr[1];

3. Self-Reference의 반환
: this가 자신을 가리키는 포인터라는 것은 다 알것이다. 
Self-Reference는 자신을 참조할 수 있는 참조자(Reference) 이다. 우리는 this 포인터를 이용해서 객체가 자신의 참조에 사용할 수 있는 참조자의 반환문을 구성할 수 있다. 

Class SelfRef
{
private:
    int num;
public:
    SelfRef& Adder(int n)
    {
        num += n;
        
        return *this;
    }
}

'C++' 카테고리의 다른 글

[C++] Friend와 Static 그리고 Const  (0) 2017.12.25
[C++] 복사 생성자(Copy Constructor)  (0) 2017.12.23
[C++] 클래스의 기본  (0) 2017.12.23
[C++] C언어 기반의 C++(2)  (1) 2017.12.19
[C++] C언어 기반의 C++(1)  (0) 2017.12.18
Posted by 홍성곤
,

[C++] 클래스의 기본

C++ 2017. 12. 23. 17:19

1] C++ 에서의 구조체
1. C++ 에서의 구조체 변수의 선언
C언어 에서는 구조체를 다음과 같이 선언한다. 

struct Car basicCar;
struct Car simpleCar;

위와같이 C 언어에서 struct 키워드를 포함해야 하며 생략하기 위해서는 typedef을 별도로 추가해야 한다. 하지만 C++에서는 일반 자료형 변수의 선언방식과 크게 다를바 없다. 

Car basicCar;
Car simpleCar;

2. 구조체 안에 함수, enum 삽입하기

struct Car
{
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    }

    void ShowCarState()
    {
         ...
    }
    
    void Accel()
    {
        ...
    }

    void Break()
    {
        ...
    }
};

3. 함수는 외부로 뺄 수 있다. 

struct Car
{
    char gamerID[20];
    int fuelGuage;
    int curSpeed;
    
    void showCarState();
    void Accel();
};

void Car::ShowCarState()
{
    ...
}

void Car::Accel()
{
    ...
}

int main(void)
{
    Car run99 = { "run99", 100, 0 };
    
    run99.Accel();
    run99.ShowCarState();
}

사실 구조체 안에 함수가 정의되어 있으면, 다음의 의미가 더불어 내포된다. 
"함수를 인라인으로 처리해라!"
반면, 위의 예제와 같이 함수를 구조체 밖으로 빼내면, 이러한 의미가 사라진다. 따라서 인라인의 의미를 그대로 유지하려면 다음과 같이 키워드 "inline"을 이용해서 명시적으로 지시해야 한다. 

inline void Car::showCarState()
{
    ...
}

inline void Car::Accel()
{
    ...
}


2] 클래스(Class)와 객체(Object)
1. 클래스와 구조체의 차이점
앞서 선언했던 구조체 Car를 클래스로 정의해 보겠다.

Class Car
{
    char gamerID[20];
    int furlGauge;
    int curSpeed;
    
    void ShowCarState()
    {
        ...
    }
    
    void Accel()
    {
        ...
    }
}

구조체와 다른점은 앞에 "struct" 키워드를 "class"로 바꾼것 밖에 없다. 이제 사용할때의 차이점을 알아 보겠다.

Car run99 = {"run99", 100, 0}

위 코드는 컴파일 되지 않는다. 클래스 내에 선언된 변수는 앞에 접근지정자를 지정하지 않으면 자동으로 클래스 내의 함수에서 밖에 접근하지 못한다.

이렇듯 구조체와 클래스의 유일한 차이점은 외부에서 각각의 변수 및 함수에 접근 가능하도록 하려면 접근범위를 별도로 지정해야 되는지 아닌지에 차이가 있다. (사실 더 많은 차이가 있지만, 지금은 이 정도로만 알고 넘어가자.)

2. 접근제어 지시자 
- public: 어디서든 접근 허용
- protected: 상속관계에 놓여있을 때, 자식클래스만 접근 허용
- private: 클래스 내에서만 접근 허용

1) 만약 접근제어 지시자 A가 선언되면, 그 이후에 등장하는 변수나 함수는 A에 해당하는 범위 내에서 접근이 가능하다. 
2) 그러나 새로운 접근제어 지시자 B가 선언되면, 그 이후로 등장하는 변수나 함수는 B에 해당하는 범위 내에서 접근이 가능하다.
3)  구조체에서 접근제어 지시자를 지정하지 않으면 자동으로 public 지정되고, Class에서 지정하지 않으면 자동으로 private 지정된다. 

3. 파일 분할
: C++ 에서는 클래스의 선언을 .h 파일에 담고, 정의는 .cpp 파일에 담아 파일분할을 한다. 
즉, .h 파일에는 컴파일에 필요한 정보들을 담고 .cpp 파일에는 실제 구현내용을 담는다. 
링커는 .cpp에 있는 내용들로 서로 링크시켜서 하나의 실행파일 만든다. 

*inline 함수는 헤더파일에 함께 넣어야 한다. 
: inline 함수는 컴파일러에 의해 함수 호출부분을 구현부분으로 대체해야 하기 때문에 inline 함수의 구현은 .h 파일에 위치해야 컴파일러가 적절하게 컴파일 할 수 있다. 

'C++' 카테고리의 다른 글

[C++] Friend와 Static 그리고 Const  (0) 2017.12.25
[C++] 복사 생성자(Copy Constructor)  (0) 2017.12.23
[C++] 클래스의 완성  (1) 2017.12.23
[C++] C언어 기반의 C++(2)  (1) 2017.12.19
[C++] C언어 기반의 C++(1)  (0) 2017.12.18
Posted by 홍성곤
,

[C++] C언어 기반의 C++(2)

C++ 2017. 12. 19. 10:46

1] Remind C
1. const의 의미 
const int num = 10; // 변수 num을 상수화
const int* ptr1 = &val1; // ptr1을 이용해서 val1의 값을 변경할 수 없음
int* const ptr2 = &val2; // ptr2를 상수화

2. 메모리 영역
- 데이터 영역: 전역변수가 저장되는 영역
- 스택: 지역변수 및 매개변수가 저장되는 영역
- 힙: malloc 함수 호출에 의해 프로그램이 실행되는 과정에서 동적으로 할당이 이뤄지는 영역.
- malloc & free : malloc 함수호출에 의해 할당된 메모리 공간은 free 함수호출을 통해서 소멸되지 않으면 해제되지 않는다. 


2] bool
: true와 false는 1과 0이 아니다. 

cout<<"true : "<<true<<endl;
cout<<"false : "<<false<<endl;

->
true : 1
false : 0

위 결과는 보고 true는 1, false는 0 이라고 생각할 수도 있다.

cout<<"sizeof 1: "<<sizeof(1)<<endl;
cout<<"sizeof 0: "<<sizeof(0)<<endl;
cout<<"sizeof true: "<<sizeof(true)<<endl;
cout<<"sizeof false: "<<sizeof(false)<<endl;

->
sizeof 1 : 4
sizeof 0 : 4
sizeof true : 1 
sizeof false : 1

위 결과를 보면 true, false의 크기와 0, 1의 크기가 다르다. 
true와 false가 정의되기 전에는 참과 거짓을 표현하기 위해서 1, 0으르 사용했기 때문에 이 둘을 출력하거나 정수의 형태로 형 변환하는 경우에 각각 1과 0으로 변환되도록 정의되어 있을 뿐이다. 

따라서 true와 false는 참과 거짓을 나타내는 목적으로 정의도니 데이터로 인식하는 것이 바람직하다. 


3] reference의 이해
: 할당된 하나의 메모리 공간에 다른 이름을 부여하는 것이다.

int &num2=num1;

위에서 "&"는 주소값을 반환하는 연산자가 아니라, 참조자의 선언을 뜻한다. 

int num1 = 1020;
int &num2 = num1;

num2 = 3047;

위 코드를 실행하면 num1, num2 모두 3047을 가진다. 이 결과는 하나의 메모리 공간을 똑같이 가리키니까 어찌보면 당연한 결과다.

1) 참조자(reference)를 이용한 Call-by-reference

void SwapByRef(int &ref1, int &ref2)
{
    int temp=ref1;

    ref1=ref2;
    ref2=temp;
}

2) 참조자(reference)를 이용한 Call-by-reference, 그리고 const 참조자
: 지금까지만 보면, 포인터를 이용한 Call-by-reference 보다 reference를 이용한 것이 더 간단하고 편해보인다. 하지만 단점이 있다.

int num=24;

HappyFunc(num);

위 코드에서 num의 값은 HappyFunc 함수 호출이후 어떤값이 될까? C라면 100프로 24가 되야 하지만, C++ 에서는 알수가 없다. reference를 이용한 Call-by-reference일 수도 있기 때문이다. 

이런 단점을 극복하려면 const 키워드를 사용해서 상수화 시켜야 한다. 

void HappyFunc(const int &ref) { ... }

위에서 ref는 함수내에서 값을 변경할 수 없다. 따라서, 함수 내에서 참조자를 통한 값의 변경을 진행하지 않은경우는 const 키워드를 사용해서 함수 원형만 봐도 값의 변형이 이뤄지지 않음을 알수있게 해라.

3) 반환형이 참조형(reference type)인 경우

int& RefRetFuncOne(int &ref)
{
    ref++;
    return ref;
}

int num1=1;
int &num2=RefRetFuncOne(num1);

num1++;
num2++;

cout<<"num1: "<<num1<<endl;
cout<<"num2: "<<num2<<endl;

-> 출력
num1: 4
num2: 4 

그런데 위 함수의 반환값을 int형으로 받으면 어떻게 되는지 보겠다.

num1 = 1;
int num2 = RefRetFuncOne(num1);

num1+=1;
num2+=100;

cout<<"num1: "<<num1<<endl;
cout<<"num2: "<<num2<<endl;

-> 출력
num1: 3
num2: 102

int&을 int형으로 받을 수 있고 이는 완전 다른 변수가 된다. 
그렇지만 int 반환값을 int&로 받을 수는 없다. 

다음은 잘못된 예를 들어보겠다.

int& RetuRefFunc(int n)
{
    int num = 20;
    
    num+=n;
    
    return num;
}

int &ref = RetuRefFunc(10);

int를 return 하지만 함수의 반환형으로 인해 int& 타입이 return 된다. 
그래서 ref 변수는 num 변수의 메모리를 같이 참조하게 되지만, num은 지역변수 이기 때문에 반환되어 ref 변수는 쓰레기값을 가지게 된다. 

4) 참조자(reference)의 상수 참조
: 리터럴 상수를 reference가 참조하려면 const 키워드를 사용해야 한다. 

const int *ref = 30;

위와같은 코드가 과연 필요한지 의문이 들기는 하지만 굳이 사용법을 찾아보겠다.

int Adder(const int& num1, const int& num2)
{
    return num1+num2;
}

위 함수에서 매개변수 둘을 더한값을 return하기 위해 변수 하나를 더 만드는것이 귀찮기 때문에 저런식으로 사용한 것이다. 


4] malloc & free를 대신하는 new & delete
: malloc, free를 사용해서 heap에 메모리를 할당, 해제하는것보다 조금 더 간단해졌다. 

int* ptr1 = new int; // int형 변수의 할당
double* ptr2 = new double; // double형 변수의 할당
int* arr1 = new int[3]; // 길이가 3인 int 형 배열의 할당
double * arr2 = new double[7]; //길이가 7인 double형 배열의 할당

delete ptr1;
delete ptr2;
delete []arr1;
delete []arr2;

* C++ 에서는 malloc, free 대신에 꼭 new, delete를 사용하자. 둘은 사용방식에 차이가 있어서 C++에서 malloc, free의 사용이 오류를 일으킬 수 있다. 

* heap에 할당된 변수도 참조자(reference)로 접근할 수 있다. 

int* ptr = new int;
int& ref = (*ptr);

ref = 20;

cout<<(*ptr)<<endl;

-> 출력
20




'C++' 카테고리의 다른 글

[C++] Friend와 Static 그리고 Const  (0) 2017.12.25
[C++] 복사 생성자(Copy Constructor)  (0) 2017.12.23
[C++] 클래스의 완성  (1) 2017.12.23
[C++] 클래스의 기본  (0) 2017.12.23
[C++] C언어 기반의 C++(1)  (0) 2017.12.18
Posted by 홍성곤
,